dscf-marketplace 0.6.4 → 0.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 594b8a0c6b3e5d673cc49e5b9fed9944ed8741a9e060878b7db0faf18b425316
4
- data.tar.gz: 3a4ae57c850ebc4f616def389b9b66d3d1aebe75eed35a0920fac405bcd34789
3
+ metadata.gz: 0d395ee1a2d1134af4a10cf72e0591583e898e507cf94432d62849c992d888ec
4
+ data.tar.gz: 4cb118146a4759d47d069d34afd0aa4b857329c4eaa28509bb62b35301c9a7b2
5
5
  SHA512:
6
- metadata.gz: 25512776a17bd4984353716c8020a13d3ce77b510c68cc7b8b139bc19f4c3b3145ee55cd61a788c1a21e96f3d121cd5dddfe12a62f2359a01d1a5b3d008400d1
7
- data.tar.gz: 2b0e4ed3a9fc273e0cfee298f8fcd59f714494dd85cbf818d5ba9dd6ff124270ee200724e53be170f1af5fb9f0ffc71dffa97cdfc680aee10a1b1926c617a82d
6
+ metadata.gz: 7d82bd17842e98ef5880bdf7b1396722346b1cb60722ab4cb1db52e21ca1680b97b2766d24d1f8bb7c3df70c080a17dd72f2a08d8bd67e8a0ebf88bae10ebe42
7
+ data.tar.gz: 6d6793f1ca44cb0976147056bcfccf23c88720109f04d7590460ccf81baba4dba272bd06423f054e7a759fbc80581e2e36415a5bca853af7c27fddb750c17028
@@ -36,8 +36,8 @@ module Dscf::Marketplace
36
36
  create_delivery_stops_and_items(delivery_order, orders, pickup_address)
37
37
  associate_orders_with_delivery(delivery_order, orders)
38
38
 
39
- # Optimize route - Removed as per requirement
40
- # RouteOptimizationService.new.optimize_route(delivery_order)
39
+ # Optimize route using Gebeta Maps
40
+ RouteOptimizationService.new(delivery_order).optimize!
41
41
 
42
42
  delivery_order
43
43
  end
@@ -0,0 +1,38 @@
1
+ require "httparty"
2
+
3
+ module Dscf
4
+ module Marketplace
5
+ class GebetaService
6
+ include HTTParty
7
+ base_uri "https://mapapi.gebeta.app/api"
8
+ debug_output $stdout if Rails.env.development?
9
+
10
+ def initialize(api_key: ENV["GEBETA_MAPS_API_KEY"])
11
+ @api_key = api_key
12
+ end
13
+
14
+ # Traveling Salesperson Solver
15
+ # @param locations [Array<Array>] Array of coordinate pairs [[lat, lng], [lat, lng], ...]
16
+ # Start point is assumed to be the first element.
17
+ def tsp(locations)
18
+ # Gebeta TSS API expects a custom string format: [{lat,lon},{lat,lon},...]
19
+ # Example: [{8.9,38.7},{9.0,38.8}] - Literal curly braces, comma sep lat/lon
20
+
21
+ custom_payload = "[" + locations.map { |loc| "{#{loc[0]},#{loc[1]}}" }.join(",") + "]"
22
+
23
+ # We pass this string directly to the json query param.
24
+ # HTTParty will encode the characters (e.g., { becomes %7B), which is expected.
25
+ response = self.class.get("/route/tss", query: {
26
+ apiKey: @api_key,
27
+ json: custom_payload
28
+ })
29
+
30
+ if response.success?
31
+ response.parsed_response
32
+ else
33
+ raise StandardError, "Gebeta API Error: #{response.code} - #{response.body}"
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,94 @@
1
+ module Dscf
2
+ module Marketplace
3
+ class RouteOptimizationService
4
+ attr_reader :delivery_order
5
+
6
+ def initialize(delivery_order)
7
+ @delivery_order = delivery_order
8
+ end
9
+
10
+ def optimize!
11
+ return unless delivery_order.pickup_address && delivery_order.delivery_stops.any?
12
+
13
+ # 1. Collect Coordinates
14
+ # Format: [[pickup_lat, pickup_lng], [stop1_lat, stop1_lng], ...]
15
+ pickup_coords = [delivery_order.pickup_address.latitude.to_f, delivery_order.pickup_address.longitude.to_f]
16
+
17
+ # We map stops to their dropoff coordinates.
18
+ # We need to keep track of the mapping between original index and stop ID to re-order later.
19
+ stops = delivery_order.delivery_stops.includes(:dropoff_address).to_a
20
+ stop_coords = stops.map { |stop| [stop.dropoff_address.latitude.to_f, stop.dropoff_address.longitude.to_f] }
21
+
22
+ all_locations = [pickup_coords] + stop_coords
23
+
24
+ # 2. Call Gebeta API
25
+ response = GebetaService.new.tsp(all_locations)
26
+
27
+ # 3. Process Response
28
+
29
+ # New Response Format:
30
+ # {
31
+ # "best_order": [
32
+ # {"lat":..., "lon":...}, # Start (Pickup)
33
+ # {"lat":..., "original_index": 2}, # Stop A
34
+ # {"lat":..., "original_index": 1} # Stop B
35
+ # ],
36
+ # "total_distance": 13.474, # In KM? Diagnostic showed 13.474 for small distance. Need to verify unit.
37
+ # "time_taken": 1033.263,
38
+ # "Direction": [[lat,lon], ...]
39
+ # }
40
+
41
+ # 'best_order' includes the start point at index 0 (usually without original_index or it's 0/1 based?)
42
+ # Based on diagnostic: "original_index": 2 for the 3rd point (index 2).
43
+ # So original_index is 0-based index from the input array.
44
+
45
+ best_order = response["best_order"]
46
+
47
+ # Filter out the pickup location (which should be the first one, or original_index == 0)
48
+ # We only want to re-sequence the STOPS.
49
+ # Stops in 'stops' array correspond to input indices 1..N.
50
+
51
+ ordered_stops_data = best_order.select { |node| node["original_index"].to_i > 0 }
52
+
53
+ ActiveRecord::Base.transaction do
54
+ ordered_stops_data.each_with_index do |node, seq_num|
55
+ original_idx = node["original_index"]
56
+ # stops array is 0-indexed, corresponding to input indices 1, 2, 3...
57
+ # If original_idx is 1, it means stops[0]. If 2, stops[1].
58
+ stop = stops[original_idx - 1]
59
+
60
+ if stop
61
+ stop.update!(sequence_number: seq_num + 1)
62
+ end
63
+ end
64
+
65
+ # Update DeliveryOrder metrics
66
+ # Check units:
67
+ # time_taken: 1033.263 (likely seconds ~ 17 mins for decent distance).
68
+ # total_distance: 13.474. If this is KM, it's reasonable. (Distance between 9.02,38.80 and 9.028,38.75 is ~ small).
69
+ # If meters, 13 meters is too small. 1000 seconds for 13 meters is wrong.
70
+ # So total_distance is likely KM.
71
+
72
+ distance_km = response["total_distance"].to_f
73
+
74
+ delivery_order.update!(
75
+ estimated_delivery_time: Time.current + response["time_taken"].to_f.seconds,
76
+ estimated_delivery_price: calculate_price(distance_km * 1000), # pass meters to calc
77
+ optimized_route: response["Direction"]
78
+ )
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ # Placeholder pricing logic
85
+ def calculate_price(distance_meters)
86
+ base_rate = 50.0
87
+ km_rate = 10.0 # 10 ETB per km
88
+ distance_km = distance_meters / 1000.0
89
+
90
+ base_rate + (distance_km * km_rate)
91
+ end
92
+ end
93
+ end
94
+ end
@@ -1,5 +1,5 @@
1
1
  module Dscf
2
2
  module Marketplace
3
- VERSION = "0.6.4".freeze
3
+ VERSION = "0.6.5".freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dscf-marketplace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.4
4
+ version: 0.6.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Asrat
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-12-12 00:00:00.000000000 Z
10
+ date: 2025-12-20 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -472,9 +472,11 @@ files:
472
472
  - app/serializers/dscf/marketplace/user_serializer.rb
473
473
  - app/services/dscf/marketplace/delivery_order_service.rb
474
474
  - app/services/dscf/marketplace/dispute_service.rb
475
+ - app/services/dscf/marketplace/gebeta_service.rb
475
476
  - app/services/dscf/marketplace/my_resource_service.rb
476
477
  - app/services/dscf/marketplace/rfq_response_service.rb
477
478
  - app/services/dscf/marketplace/role_service.rb
479
+ - app/services/dscf/marketplace/route_optimization_service.rb
478
480
  - config/environments/production.rb
479
481
  - config/locales/en.yml
480
482
  - config/locales/en.yml.backup