chore: init commit

in worker.../repo/GITFOLDER.zip is the .git folder.
This commit is contained in:
2026-08-11 14:44:09 -04:00
parent 0012380fd3
commit 2854619bc9
782 changed files with 65944 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
# frozen_string_literal: true
require "net/http"
require "json"
class AlphaVantageApiClient
class ApiError < StandardError; end
class InvalidResponseError < ApiError; end
def initialize
@api_key = ENV.fetch("ALPHA_VANTAGE_API_KEY", nil)
end
def fetch_quote(symbol)
return nil unless api_key_present?
Rails.logger.debug { "Fetching quote for #{symbol}" }
data = make_request(symbol)
extract_price_data(data, symbol)
rescue JSON::ParserError => e
Rails.logger.error "Failed to parse API response for #{symbol}: #{e.message}"
nil
rescue StandardError => e
Rails.logger.error "API request failed for #{symbol}: #{e.message}"
nil
end
private
def api_key_present?
return true if @api_key
Rails.logger.error "ALPHA_VANTAGE_API_KEY environment variable not configured"
false
end
def make_request(symbol)
url = build_url(symbol)
uri = URI.parse(url)
response = Net::HTTP.get(uri)
JSON.parse(response)
end
def build_url(symbol)
"https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=#{symbol}&apikey=#{@api_key}"
end
def extract_price_data(data, symbol)
price = data&.dig("Global Quote", "05. price")
trading_day = data&.dig("Global Quote", "07. latest trading day")
if price && trading_day
Rails.logger.info "API returned price #{price} for #{symbol} (trading day: #{trading_day})"
return { price: price, trading_day: trading_day }
end
Rails.logger.error "Invalid API response for #{symbol}: #{data}"
nil
end
end

View File

@@ -0,0 +1,36 @@
# frozen_string_literal: true
class BulkStudentImportService
ResultWithLineNumber = Struct.new(:result, :line_number) do
delegate_missing_to :result
end
def self.import_from_csv(csv_file_path)
results = []
CSV.foreach(csv_file_path, headers: true).with_index(2) do |row, csv_line_number|
classroom_id = row["classroom_id"]&.strip
username = row["username"]&.strip
next if classroom_id.blank? || username.blank?
result = ImportStudentService.call(
username: username,
classroom_id: classroom_id
)
results << ResultWithLineNumber.new(result: result, line_number: csv_line_number)
end
results
end
def self.generate_csv_template
[
"classroom_id,username",
"1,student001",
"1,student002",
"2,student003"
].join("\n")
end
end

View File

@@ -0,0 +1,74 @@
# frozen_string_literal: true
class DistributeEarnings
def initialize(grade_book)
@grade_book = grade_book
@previous_entries = find_previous_entries
end
def self.execute(...)
new(...).execute
end
def execute
return unless @grade_book.verified?
ActiveRecord::Base.transaction do
distribute_funds_to_students
@grade_book.completed!
end
end
private
def distribute_funds_to_students
@grade_book.grade_entries.each do |entry|
previous_entry = @previous_entries[entry.user_id]&.first
distribute_earnings(entry.user, attendance_earnings(entry, previous_entry), :attendance_earnings)
distribute_earnings(entry.user, math_earnings(entry, previous_entry), :math_earnings)
distribute_earnings(entry.user, reading_earnings(entry, previous_entry), :reading_earnings)
end
end
def find_previous_entries
previous_quarter = @grade_book.quarter.previous
return {} unless previous_quarter
previous_grade_book = GradeBook.find_by(classroom: @grade_book.classroom, quarter: previous_quarter)
return {} unless previous_grade_book
previous_grade_book.grade_entries.to_a.group_by(&:user_id)
end
def distribute_earnings(user, amount_cents, reason_key)
return if amount_cents.zero?
user.portfolio.portfolio_transactions.create!(
amount_cents: amount_cents,
transaction_type: :deposit,
reason: reason_key
)
end
def attendance_earnings(entry, _previous_entry)
[
entry.earnings_for_attendance,
entry.attendance_perfect_earnings
].sum
end
def math_earnings(entry, previous_entry)
[
entry.earnings_for_math,
entry.math_improvement_earnings(previous_entry)
].sum
end
def reading_earnings(entry, previous_entry)
[
entry.earnings_for_reading,
entry.reading_improvement_earnings(previous_entry)
].sum
end
end

View File

@@ -0,0 +1,75 @@
# frozen_string_literal: true
class ExecuteOrder
delegate :portfolio, :shares, :stock, to: :order, private: true
delegate :price_cents, to: :stock, prefix: true, private: true
def initialize(order)
@order = order
end
def self.execute(...)
new(...).execute
end
def execute
return unless order.pending?
if order.buy? && insufficient_funds?
order.cancel!
return
end
if order.sell? && insufficient_shares?
order.cancel!
return
end
ActiveRecord::Base.transaction do
create_portfolio_transaction
create_portfolio_stock
update_order_status
end
end
private
attr_accessor :order, :portfolio_stock, :portfolio_transaction
def create_portfolio_transaction
@portfolio_transaction = if order.buy?
portfolio
.portfolio_transactions
.debit
.create!(amount_cents: purchase_cost)
else
portfolio
.portfolio_transactions
.credit
.create!(amount_cents: purchase_cost)
end
end
def create_portfolio_stock
share_amount = order.sell? ? -shares : shares
@portfolio_stock = portfolio
.portfolio_stocks
.create!(stock:, shares: share_amount, purchase_price: stock.current_price)
end
def update_order_status
order.update!(status: :completed, portfolio_stock:, portfolio_transaction:)
end
def insufficient_funds?
portfolio.cash_balance.negative?
end
def insufficient_shares?
portfolio.shares_owned(stock.id) < shares
end
def purchase_cost
order.purchase_cost
end
end

View File

@@ -0,0 +1,81 @@
# frozen_string_literal: true
class ImportStudentService
Result = Struct.new(:success?, :student, :error_message, :action) do
def created?
action == :created
end
def skipped?
action == :skipped
end
def failed?
action == :failed
end
end
def self.call(username:, classroom_id:)
new.call(username: username, classroom_id: classroom_id)
end
def call(username:, classroom_id:)
username = sanitize_input(username)
classroom_id = sanitize_input(classroom_id)
return skip_result("Username is required") if username.blank?
return skip_result("Student with username '#{username}' already exists") if Student.exists?(username: username)
return skip_result("Classroom ID is required") if classroom_id.blank?
import_student(username: username, classroom_id: classroom_id)
end
private
def sanitize_input(input)
input&.to_s&.strip
end
def import_student(username:, classroom_id:)
student = Student.new(
username: username,
classroom_id: classroom_id,
password: MemorablePasswordGenerator.generate
)
if student.save
success_result(student)
else
error_result(student)
end
rescue ActiveRecord::InvalidForeignKey
student.errors.add(:classroom, "can't be blank")
error_result(student)
end
def success_result(student)
Result.new(
success?: true,
student: student,
error_message: nil,
action: :created
)
end
def skip_result(message)
Result.new(
success?: true,
student: nil,
error_message: message,
action: :skipped
)
end
def error_result(student)
Result.new(
success?: false,
student: student,
error_message: student.errors.full_messages.join(", "),
action: :failed
)
end
end

View File

@@ -0,0 +1,21 @@
# frozen_string_literal: true
# TODO: more robust solution later.
class MemorablePasswordGenerator
RANGE = (1..99)
class << self
def generate
password = Faker::Superhero.name + rand(RANGE).to_s + Faker::Superhero.name
normalize password
end
private
def normalize(password)
invalid_characters = [" ", "-", "'"]
invalid_characters.each { password.delete! it }
password
end
end
end

View File

@@ -0,0 +1,82 @@
# frozen_string_literal: true
require "net/http"
require "json"
class StockAttributeUpdate
def initialize(stock)
@stock = stock
end
def self.execute(stock)
new(stock).execute
end
def execute
return if stock.ticker.blank?
log_start_update
data = fetch_api_data
return unless valid_api_response?(data)
update_stock_attributes(data)
save_stock
end
private
attr_reader :stock
def log_start_update
Rails.logger.info "Updating attributes for stock #{stock.ticker}"
end
def fetch_api_data
api_request(stock.ticker)
end
def valid_api_response?(data)
if data && data["Symbol"]
Rails.logger.info "API returned data for #{stock.ticker}"
true
else
Rails.logger.error "Invalid API response for #{stock.ticker}: #{data}"
false
end
end
def save_stock
if stock.save
Rails.logger.info "Successfully updated attributes for #{stock.ticker}"
else
Rails.logger.error "Failed to save #{stock.ticker}: #{stock.errors.full_messages}"
end
end
def update_stock_attributes(data)
update_basic_info(data)
update_financial_info(data)
end
def update_basic_info(data)
stock.company_name = data["Name"] if data["Name"]
stock.description = data["Description"] if data["Description"]
stock.stock_exchange = data["Exchange"] if data["Exchange"]
stock.industry = data["Industry"] if data["Industry"]
end
def update_financial_info(data)
stock.company_website = data["OfficialSite"] if data["OfficialSite"]
stock.profit_margin = data["ProfitMargin"].to_f if data["ProfitMargin"]
end
def api_request(symbol)
url = "https://www.alphavantage.co/query?function=OVERVIEW&symbol=#{symbol}&apikey=#{API_KEY}"
uri = URI.parse(url)
JSON.parse Net::HTTP.get(uri)
rescue JSON::ParserError, Net::HTTPError => e
Rails.logger.error "API request failed for #{symbol}: #{e.message}"
nil
end
end

View File

@@ -0,0 +1,32 @@
# frozen_string_literal: true
class TransactionFeeProcessor
def initialize(orders)
@orders = orders
@users = Set.new
end
def self.execute(...)
new(...).execute
end
def execute
ActiveRecord::Base.transaction do
@orders.each do |order|
add_transaction_fee order
end
end
end
private
def add_transaction_fee(order)
return if @users.include?(order.portfolio.user_id)
order.portfolio.portfolio_transactions.fees.create!(
amount_cents: PortfolioTransaction::TRANSACTION_FEE_CENTS,
reason: :transaction_fees
)
@users << order.portfolio.user_id
end
end