This is a system that is able to load, parse, search, and execute business intelligence queries against the data from a typical e-commerce business.
The project has two parts; data and analysis.
The DAL is built using the SalesEngine class which loads and parses the raw data from CSV files.
The SalesEngine
is instantiated with the CSV files. The below repositories and SalesAnalyst
are instantiated with the SalesEngine with the following repositories:
SalesEngine.from_csv({
:items => './data/items.csv',
:merchants => './data/merchants.csv',
:invoices => './data/invoices.csv',
:transactions => './data/transactions.csv',
:invoice_items => './data/invoice_items.csv',
:customers => './data/customers.csv'
})
The MerchantRepository
is responsible for holding and searching our Merchant
instances. It offers the following methods:
all
- returns an array of all knownMerchant
instancesfind_by_id
- returns eithernil
or an instance ofMerchant
with a matching IDfind_by_name
- returns eithernil
or an instance ofMerchant
having done a case insensitive searchfind_all_by_name
- returns either[]
or one or more matches which contain the supplied name fragment, case insensitive
An example of a merchant instance:
Merchant.new({
:id => 5,
:name => "Turing School"
})
The ItemRepository
is responsible for holding and searching our Item
instances. It offers the following methods:
all
- returns an array of all knownItem
instancesfind_by_id
- returns eithernil
or an instance ofItem
with a matching IDfind_by_name
- returns eithernil
or an instance ofItem
having done a case insensitive searchfind_all_with_description
- returns either[]
or instances ofItem
where the supplied string appears in the item description (case insensitive)find_all_by_price
- returns either[]
or instances ofItem
where the supplied price exactly matchesfind_all_by_price_in_range
- returns either[]
or instances ofItem
where the supplied price is in the supplied range (a single Rubyrange
instance is passed in)find_all_by_merchant_id
- returns either[]
or instances ofItem
where the supplied merchant ID matches that supplied
Item.new({
:name => "Pencil",
:description => "You can use it to write things",
:unit_price => BigDecimal.new(10.99,4)
})
The InvoiceRepository
is responsible for holding and searching our Invoice
instances. It offers the following methods:
all
- returns an array of all knownInvoice
instancesfind_by_id
- returns eithernil
or an instance ofInvoice
with a matching IDfind_all_by_customer_id
- returns either[]
or one or more matches which have a matching customer IDfind_all_by_merchant_id
- returns either[]
or one or more matches which have a matching merchant IDfind_all_by_status
- returns either[]
or one or more matches which have a matching status
Invoice.new({
:id => 6,
:customer_id => 7,
:merchant_id => 8,
:status => "pending"
})
Invoice items are how invoices are connected to items. A single invoice item connects a single item with a single invoice.
The InvoiceItemRepository
is responsible for holding and searching our InvoiceItem
instances. It offers the following methods:
all
- returns an array of all knownInvoiceItem
instancesfind_by_id
- returns eithernil
or an instance ofInvoiceItem
with a matching IDfind_all_by_item_id
- returns either[]
or one or more matches which have a matching item IDfind_all_by_invoice_id
- returns either[]
or one or more matches which have a matching invoice ID
InvoiceItem.new({
:id => 6,
:item_id => 7,
:invoice_id => 8,
:quantity => 1,
:unit_price => BigDecimal.new(10.99, 4)
})
It also offers the following method:
unit_price_to_dollars
- returns the price of the invoice item in dollars formatted as aFloat
Transactions are billing records for an invoice. An invoice can have multiple transactions, but should have at most one that is successful.
The TransactionRepository
is responsible for holding and searching our Transaction
instances. It offers the following methods:
all
- returns an array of all knownTransaction
instancesfind_by_id
- returns eithernil
or an instance ofTransaction
with a matching IDfind_all_by_invoice_id
- returns either[]
or one or more matches which have a matching invoice IDfind_all_by_credit_card_number
- returns either[]
or one or more matches which have a matching credit card numberfind_all_by_result
- returns either[]
or one or more matches which have a matching status
Transaction.new({
:id => 6,
:invoice_id => 8,
:credit_card_number => "4242424242424242",
:credit_card_expiration_date => "0220",
:result => "success"
})
Customers represent a person who's made one or more purchases in our system.
The CustomerRepository
is responsible for holding and searching our Customers
instances. It offers the following methods:
all
- returns an array of all knownCustomers
instancesfind_by_id
- returns eithernil
or an instance ofCustomer
with a matching IDfind_all_by_first_name
- returns either[]
or one or more matches which have a first name matching the substring fragment suppliedfind_all_by_last_name
- returns either[]
or one or more matches which have a last name matching the substring fragment supplied
Customer.new({
:id => 6,
:first_name => "Joan",
:last_name => "Clarke"
})
From a technical perspective, this project emphasizes:
- File I/O
- Relationships between objects
- Encapsulating Responsibilities
- Light data / analytics
The Analysis Layer execute business intelligence queries against the data. The SalesAnalyst class is instantiated with the SalesEngine which gives it access to the data repositories.
se = SalesEngine.from_csv({
:items => "./data/items.csv",
:merchants => "./data/merchants.csv",
})
sa = SalesAnalyst.new(se)
sa.average_items_per_merchant # => 2.88
sa.average_items_per_merchant_standard_deviation # => 3.26
sa.merchants_with_high_item_count # => [merchant, merchant, merchant]
sa.average_item_price_for_merchant(6) # => BigDecimal
sa.average_average_price_per_merchant # => BigDecimal
sa.golden_items # => [<item>, <item>, <item>, <item>]
sa.average_invoices_per_merchant # => 8.5
sa.average_invoices_per_merchant_standard_deviation # => 1.2
sa.top_merchants_by_invoice_count # => [merchant, merchant, merchant]
sa.bottom_merchants_by_invoice_count # => [merchant, merchant, merchant]
sa.top_days_by_invoice_count # => ["Sunday", "Saturday"]
sa.invoice_status(:pending) # => 5.25
sa.invoice_status(:shipped) # => 93.75
sa.invoice_status(:returned) # => 1.00
# invoice.transactions.map(&:result) #=> ["failed", "success"]
invoice.is_paid_in_full? #=> true
# invoice.transactions.map(&:result) #=> ["failed", "failed"]
invoice.is_paid_in_full? #=> false
sa.top_buyers(x) #=> [customer, customer, customer, customer, customer]
sa.top_buyers #=> [customer * 20]
sa.top_merchant_for_customer(customer_id) #=> merchant
sa.one_time_buyers #=> [customer, customer, customer]
sa.one_time_buyers_top_items #=> [item]
sa.items_bought_in_year(customer_id, year) #=> [item]
sa.highest_volume_items(customer_id) #=> [item, item, item]
sa.customers_with_unpaid_invoices #=> [customer, customer, customer]
sa.best_invoice_by_revenue #=> invoice
sa.best_invoice_by_quantity #=> invoice
- Ruby (version 2.3)