Task 4 — Exchange Office Network
Objective
Create a model for an exchange-office network.
The system contains two exchange markets with different exchange rates.
Users submit transactions that convert money from one currency into another.
For every transaction, the implementation must:
- determine the requested currency pair
- compare exchange rates between markets
- select the better exchange market
- calculate the converted amount
- identify the user account
- credit the converted amount to the destination-currency sector
- produce a structured exchange result
- produce logs
- print output in JSON format
Supported Currencies
The system supports:
GBP — UK Pound
USD — US Dollar
EUR — Euro
RUB — Russian Ruble
JPY — Japanese Yen
INR — Indian Rupee
A typed currency representation is recommended.
For example:
type Currency string
const (
CurrencyGBP Currency = "GBP"
CurrencyUSD Currency = "USD"
CurrencyEUR Currency = "EUR"
CurrencyRUB Currency = "RUB"
CurrencyJPY Currency = "JPY"
CurrencyINR Currency = "INR"
)
Exchange Market
Each exchange market contains rates for supported currency transformations.
A possible model is:
type ExchangeMarket struct {
ID int
Rates []ExchangeRate
}
with:
type ExchangeRate struct {
From Currency
To Currency
Rate float64
}
For example:
GBP -> USD = 1.189
means:
1 GBP = 1.189 USD
Exchange Market 1
Use the following rates:
GBP -> USD = 1.189
GBP -> EUR = 1.184
GBP -> RUB = 77.294
GBP -> JPY = 163.314
GBP -> INR = 94.480
USD -> GBP = 0.840
USD -> EUR = 0.995
USD -> RUB = 65.000
USD -> JPY = 137.265
USD -> INR = 79.435
EUR -> GBP = 0.844
EUR -> USD = 1.004
EUR -> RUB = 65.279
EUR -> JPY = 137.790
EUR -> INR = 79.768
RUB -> GBP = 0.012
RUB -> USD = 0.015
RUB -> EUR = 0.015
RUB -> JPY = 2.110
RUB -> INR = 1.222
JPY -> GBP = 0.006
JPY -> USD = 0.007
JPY -> EUR = 0.007
JPY -> RUB = 0.473
JPY -> INR = 0.578
INR -> GBP = 0.010
INR -> USD = 0.012
INR -> EUR = 0.012
INR -> RUB = 0.818
INR -> JPY = 1.727
Exchange Market 2
Use the following rates:
GBP -> USD = 1.180
GBP -> EUR = 1.189
GBP -> RUB = 76.294
GBP -> JPY = 164.314
GBP -> INR = 93.480
USD -> GBP = 0.860
USD -> EUR = 0.999
USD -> RUB = 64.000
USD -> JPY = 136.265
USD -> INR = 80.435
EUR -> GBP = 0.824
EUR -> USD = 1.000
EUR -> RUB = 66.279
EUR -> JPY = 138.790
EUR -> INR = 80.768
RUB -> GBP = 0.082
RUB -> USD = 0.010
RUB -> EUR = 0.018
RUB -> JPY = 2.010
RUB -> INR = 1.122
JPY -> GBP = 0.004
JPY -> USD = 0.009
JPY -> EUR = 0.010
JPY -> RUB = 0.495
JPY -> INR = 0.555
INR -> GBP = 0.012
INR -> USD = 0.010
INR -> EUR = 0.011
INR -> RUB = 0.805
INR -> JPY = 1.740
Exchange Rate Data Source
The exchange-rate tables must first be transformed into JSON.
The application must then load the exchange-market data from that JSON file.
A possible JSON structure is:
[
{
"marketID": 1,
"rates": [
{
"from": "GBP",
"to": "USD",
"rate": 1.189
}
]
}
]
The exact JSON structure is left to the implementation.
The important requirement is that the rates are loaded from a file rather than hard-coded directly into processing logic.
Selecting the Best Exchange Market
For a transaction:
GBP -> USD
compare the GBP-to-USD rate from every market.
Market 1:
1.189
Market 2:
1.180
For the same source amount, the larger rate produces more destination currency.
Therefore:
Market 1
is the better market for this transformation.
For:
GBP -> EUR
the rates are:
Market 1 = 1.184
Market 2 = 1.189
Therefore:
Market 2
is better.
User Account
Each account contains:
Username
Account ID
Money stored in six currency sectors
The currency-sector mapping is:
Sector 0 -> USD
Sector 1 -> GBP
Sector 2 -> EUR
Sector 3 -> RUB
Sector 4 -> JPY
Sector 5 -> INR
A possible model is:
type Account struct {
Username string
AccountID uint64
Balances map[Currency]float64
}
A typed currency map is preferable to accessing sectors using unexplained integer indexes throughout the application.
Account Data
Use the following accounts:
Alex |2453425652|50000 |60000 |70000 |5000000|6000000|8000000
Ben |8674652543|60000 |70000 |80000 |6000000|7000000|10000000
David |8562953754|30000 |40000 |30000 |3000000|4000000|20000000
Emma |848563421 |10000 |10000 |10000 |1000000|1000000|10000000
Fiona |335647689 |5000 |5000 |5000 |100000 |100000 |1000000
Gina |242564578 |4000 |4000 |4000 |50000 |50000 |500000
Alex |667783546 |0 |0 |0 |0 |0 |0
Tina |565283951 |0 |0 |0 |0 |0 |0
Simon |4677859941|500000 |600000 |700000 |0 |0 |0
Selena |4877639720|250000 |350000 |450000 |0 |0 |0
Nolan |7445668990|0 |0 |0 |2500000|4500000|7500000
Naomi |352678541 |0 |0 |0 |7500000|8500000|3500000
Paul |9122387545|10000000|10000000|10000000 |0 |0 |0
Lana |8766234490|750000 |2000000 |10000000 |0 |0 |0
Nolan |2556779340|0 |2500000 |2500000 |0 |0 |0
The balance column order is:
USD
GBP
EUR
RUB
JPY
INR
Transaction Model
Each transaction contains:
Source Currency
Destination Currency
Amount
A possible model is:
type Transaction struct {
From Currency
To Currency
Amount float64
}
A user’s transaction request also needs account identity.
For example:
type TransactionRequest struct {
RequestID string
Username string
AccountID uint64
Transaction Transaction
}
Transaction List
Use the following requests:
Alex |2453425652|GBP-USD-500000
Ben |8674652543|GBP-EUR-750000
David |8562953754|USD-EUR-800000
Ema |848563421 |USD-JPY-900000
Fiona |335647689 |EUR-RUB-1000000
Gina |242564578 |EUR-INR-1200000
Alex |667783546 |RUB-GBP-15000000
Tina |565283951 |RUB-USD-17500000
Simon |4677859941|JPY-GBP-25000000
Selena|4877639720|JPY-EUR-30000000
Nolan |7445668990|INR-RUB-5000000
Naomi |352678541 |INR-JPY-8000000
Paul |9122387545|USD-EUR-650000|GBP-EUR-850000
Lana |8766234490|RUB-GBP-16500000|RUB-USD-19500000
Nolan |2556779340|JPY-GBP-32000000|JPY-EUR-36500000
Some accounts contain more than one transaction.
The implementation must process every listed transaction.
Source Data Inconsistency
The account table contains:
Emma | 848563421
while the transaction table contains:
Ema | 848563421
The Account ID is the same.
The implementation should not silently assume that arbitrary username differences are equivalent.
A reasonable solution is to use:
AccountID
as the primary account identifier and report the username mismatch as a data inconsistency.
Exchange Calculation
For:
amount = A
rate = R
the converted amount is:
converted = A * R
For example:
500000 GBP
GBP -> USD
best rate = 1.189
produces:
500000 * 1.189 = 594500 USD
Destination Sector Update
After conversion, the converted amount must be sent to the account sector corresponding to the destination currency.
For:
GBP -> USD
the resulting money belongs to:
USD
Sector 0
For:
RUB -> GBP
the result belongs to:
GBP
Sector 1
Important Source Limitation
The source explicitly states that the converted amount must be sent to the destination account sector.
It does not explicitly define:
- whether the original source amount must be debited
- whether insufficient source balance should reject a transaction
- whether overdrafts are allowed
Several supplied transaction amounts are also larger than the corresponding example account balances.
Therefore the base task should not silently invent insufficient-funds behavior.
If source-balance deduction is implemented, it should be clearly documented as an extension to the original task.
Exchange Query Result
Each processed exchange query must contain:
Query ID
Request Person
Source Currency
Destination Currency
Original Amount
Selected Market
Best Exchange Rate
Converted Amount
Destination Sector
A possible model is:
type ExchangeResult struct {
RequestID string
Username string
AccountID uint64
From Currency
To Currency
SourceAmount float64
MarketID int
ExchangeRate float64
ConvertedAmount float64
DestinationSector int
}
Multiple Transactions
Accounts such as:
Paul
Lana
Nolan
contain multiple exchange operations.
Every transaction must be independently evaluated because different currency pairs may select different exchange markets.
Equal Exchange Rates
The source does not define what should happen when both markets have the same rate.
The implementation must define a deterministic rule.
For example:
select the market with the lowest Market ID
This rule should be documented.
Logging
The implementation must create logs for exchange processing.
Useful log information includes:
request ID
account ID
currency pair
selected market
exchange rate
converted amount
processing result
The exact logging framework is left to the implementation.
JSON Output
The final exchange result must be printable in JSON format.
Example:
{
"requestID": "exchange-1",
"username": "Alex",
"accountID": 2453425652,
"from": "GBP",
"to": "USD",
"sourceAmount": 500000,
"marketID": 1,
"exchangeRate": 1.189,
"convertedAmount": 594500,
"destinationSector": 0
}
Validation
The implementation should detect:
unknown account
unsupported currency
invalid currency pair
missing exchange rate
invalid transaction amount
invalid market data
malformed transaction input
Transaction amounts should be greater than zero.
A transformation where:
From == To
should be rejected because no exchange operation is required.
Requirements
The implementation must:
- model supported currencies
- model exchange markets
- model exchange rates
- transform the supplied exchange-rate tables into JSON
- load exchange rates from JSON
- model user accounts
- preserve the defined currency-sector mapping
- model transaction requests
- parse all supplied transactions
- compare exchange markets
- select the best exchange rate
- calculate the converted amount
- credit the destination currency sector
- return exchange-query information
- implement processing logs
- print results in JSON format
Modeling Goal
The task combines several independent domain concepts:
ExchangeMarket
|
+-- ExchangeRate
Account
|
+-- Currency Balances
TransactionRequest
|
+-- Account
+-- Currency Pair
+-- Amount
|
v
Exchange Processor
|
+-- Select Market
+-- Convert
+-- Update Destination Sector
+-- Log
+-- JSON Result
The implementation should keep exchange-rate data, account data, transaction input, and processing logic separated rather than placing all behavior into one large structure.