Skip to content

Commit

Permalink
Merge pull request #1791 from trade-tariff/green-lanes-full-api
Browse files Browse the repository at this point in the history
GL-244 Green lanes database conversion
  • Loading branch information
jebw authored Apr 12, 2024
2 parents 9970ec8 + a512cde commit 580dfcc
Show file tree
Hide file tree
Showing 26 changed files with 741 additions and 303 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,27 @@ module V2
module GreenLanes
class CategoryAssessmentsController < BaseController
def index
category_assessments = ::GreenLanes::CategoryAssessmentJson.all
serializer = Api::V2::GreenLanes::CategoryAssessmentSerializer.new(category_assessments, include: %w[geographical_area excluded_geographical_areas])
category_assessments =
::GreenLanes::CategoryAssessment
.eager(
theme: [],
measures: {
additional_code: :additional_code_descriptions,
measure_conditions: { certificate: :certificate_descriptions },
geographical_area: :geographical_area_descriptions,
measure_excluded_geographical_areas: [],
excluded_geographical_areas: :geographical_area_descriptions,
},
)
.all

presented_assessments =
CategoryAssessmentPresenter.wrap(category_assessments)

serializer =
Api::V2::GreenLanes::CategoryAssessmentSerializer
.new(presented_assessments,
include: %w[geographical_area excluded_geographical_areas])

render json: serializer.serializable_hash
end
Expand Down
11 changes: 10 additions & 1 deletion app/models/green_lanes/category_assessment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,21 @@ class CategoryAssessment < Sequel::Model(:green_lanes_category_assessments)
plugin :timestamps, update_on_create: true
plugin :auto_validations, not_null: :presence

many_to_one :theme
many_to_one :measure_type, class: :MeasureType
many_to_one :base_regulation, class: :BaseRegulation,
key: %i[regulation_id regulation_role]
many_to_one :modification_regulation, class: :ModificationRegulation,
key: %i[regulation_id regulation_role]
many_to_one :theme
one_to_many :measures, class: :Measure,
read_only: true,
primary_key: %i[measure_type_id regulation_id regulation_role],
key: %i[measure_type_id
measure_generating_regulation_id
measure_generating_regulation_role] do |ds|
ds.with_actual(Measure)
.with_regulation_dates_query
end

def validate
super
Expand Down
4 changes: 4 additions & 0 deletions app/models/green_lanes/theme.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@ class Theme < Sequel::Model(:green_lanes_themes)
plugin :auto_validations, not_null: :presence

one_to_many :category_assessments

def to_s
"#{section}.#{subsection}. #{description}"
end
end
end
9 changes: 9 additions & 0 deletions app/models/measure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,15 @@ class Measure < Sequel::Model
ds.with_actual(FullTemporaryStopRegulation)
end

many_to_one :category_assessment,
class: 'CategoryAssessment',
class_namespace: 'GreenLanes',
read_only: true,
primary_key: %i[measure_type_id regulation_id regulation_role],
key: %i[measure_type_id
measure_generating_regulation_id
measure_generating_regulation_role]

delegate :rules_of_origin_apply?,
:third_country?,
:excise?,
Expand Down
78 changes: 76 additions & 2 deletions app/presenters/api/v2/green_lanes/category_assessment_presenter.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,91 @@
# There is a One to Many mapping between Category Assessments and their Presented
# versions
#
# This is because we include exemptions and geographical areas in the serialized
# category assessments but they can vary depending upon the measures
#
# This means the presented versions get hashed ids including the various params
# used for their differentation (the 'permutation key')
module Api
module V2
module GreenLanes
class CategoryAssessmentPresenter < SimpleDelegator
attr_reader :measures
include ContentAddressableId
attr_reader :measures, :permutation_key

def initialize(category_assessment, measures)
delegate :geographical_area_id,
:geographical_area,
:excluded_geographical_areas,
:measure_conditions,
:additional_code,
to: :first_measure

content_addressable_fields do |ca|
ca.permutation_key.map(&:to_s).join("\n")
end

class << self
def wrap(category_assessments)
Array.wrap(category_assessments).flat_map do |assessment|
permutations(assessment).map do |key, measures|
new assessment, key, measures
end
end
end

private

def permutations(assessment)
::GreenLanes::PermutationCalculatorService
.new(assessment.measures)
.call
end
end

def initialize(category_assessment, permutation_key, measures)
super(category_assessment)
@category_assessment = category_assessment
@permutation_key = permutation_key
@measures = MeasurePresenter.wrap(measures)
end

def measure_ids
@measure_ids = measures.map(&:measure_sid)
end

def theme
@category_assessment.theme.to_s
end

def category
@category_assessment.theme.category.to_s
end

def category_assessment_id
@category_assessment.id
end

def excluded_geographical_area_ids
excluded_geographical_areas.map(&:geographical_area_id)
end

def exemptions
certificates + additional_codes
end

def certificates
measure_conditions.select(&:exemption_class?).map(&:certificate)
end

def additional_codes
Array.wrap(additional_code)
end

private

def first_measure
measures.first
end
end
end
end
Expand Down
8 changes: 8 additions & 0 deletions app/presenters/api/v2/green_lanes/measure_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ def footnote_ids
def goods_nomenclature_id
@goods_nomenclature_id = goods_nomenclature.goods_nomenclature_sid
end

def exemptions
[]
end

def excluded_geographical_area_ids
[]
end
end
end
end
Expand Down
22 changes: 22 additions & 0 deletions app/services/green_lanes/fetch_goods_nomenclature_service.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
module GreenLanes
class FetchGoodsNomenclatureService
ITEM_ID_LENGTH = 10

MEASURES_EAGER = {
additional_code: :additional_code_descriptions,
goods_nomenclature: %i[goods_nomenclature_indents goods_nomenclature_descriptions],
footnotes: :footnote_descriptions,
geographical_area: %i[geographical_area_descriptions contained_geographical_areas],
measure_excluded_geographical_areas: [],
excluded_geographical_areas: :geographical_area_descriptions,
measure_conditions: { certificate: :certificate_descriptions },
category_assessment: :theme,
}.freeze

EAGER_LOAD = {
ancestors: {
measures: MEASURES_EAGER,
goods_nomenclature_descriptions: [],
},
measures: MEASURES_EAGER,
goods_nomenclature_descriptions: [],
}.freeze

def initialize(goods_nomenclature_item_id)
@goods_nomenclature_item_id = goods_nomenclature_item_id
end

def call
GoodsNomenclature
.actual
.eager(EAGER_LOAD)
.where(goods_nomenclature_item_id: length_adjusted_digit_id, producline_suffix: '80')
.take
end
Expand Down
45 changes: 30 additions & 15 deletions app/services/green_lanes/find_category_assessments_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,45 @@ def initialize(goods_nomenclature, geographical_area_id)
end

def call
CategoryAssessmentJson.all
.map(&method(:presented_matching_assessment))
.compact
@goods_nomenclature
.applicable_measures
.select(&:category_assessment)
.select(&method(:filter_by_geographical_area))
.group_by(&:category_assessment)
.flat_map do |assessment, measures_for_assessment|
compute_assessment_permutations(assessment, measures_for_assessment)
end
end

private

def assessment_matches_measure?(category_assessment, measure)
category_assessment.match?(regulation_id: measure.measure_generating_regulation_id,
measure_type_id: measure.measure_type_id,
geographical_area: @geographical_area_id)
def compute_assessment_permutations(assessment, applicable_measures)
permutations_for_assessment(applicable_measures)
.map do |key, permutation|
present_assessment(assessment, key, permutation)
end
end

def matching_measures(category_assessment)
@goods_nomenclature.applicable_measures.select do |measure|
assessment_matches_measure?(category_assessment, measure)
end
def permutations_for_assessment(applicable_measures)
PermutationCalculatorService.new(applicable_measures).call
end

def present_assessment(assessment, key, permutation)
::Api::V2::GreenLanes::CategoryAssessmentPresenter
.new(assessment, key, permutation)
end

def presented_matching_assessment(category_assessment)
matches = matching_measures(category_assessment)
return if matches.empty?
def filter_by_geographical_area(measure)
return true if @geographical_area_id.blank?

measure.relevant_for_country? geographical_area.geographical_area_id
end

::Api::V2::GreenLanes::CategoryAssessmentPresenter.new(category_assessment, matches)
def geographical_area
@geographical_area ||= GeographicalArea
.actual
.where(geographical_area_id: @geographical_area_id)
.take
end
end
end
26 changes: 26 additions & 0 deletions app/services/green_lanes/permutation_calculator_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module GreenLanes
class PermutationCalculatorService
def initialize(measures)
@measures = measures
end

def call
@measures.group_by(&method(:permutation_key))
end

private

def permutation_key(measure)
[
measure.measure_type_id,
measure.measure_generating_regulation_id,
measure.measure_generating_regulation_role,
measure.geographical_area_id,
measure.measure_excluded_geographical_areas.map(&:excluded_geographical_area).sort.uniq.join('|'),
measure.additional_code_type_id,
measure.additional_code_id,
measure.measure_conditions.select(&:exemption_class?).map(&:document_code).sort.uniq.join('|'),
]
end
end
end
3 changes: 2 additions & 1 deletion spec/factories/additional_code_factory.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
FactoryBot.define do
sequence(:additional_code_sid) { |n| n }
sequence(:additional_code_description_period_sid) { |n| n }
sequence(:additional_code_id) { |n| 'AAA'.tap { |ac| n.times { ac.next! } } }
sequence(:meursing_additional_code_sid) { |n| n }

factory :additional_code do
additional_code_sid { generate(:additional_code_sid) }
additional_code_type_id { '1' }
additional_code { Forgery(:basic).text(exactly: 3) }
additional_code { generate(:additional_code_id) }
validity_start_date { 2.years.ago.beginning_of_day }
validity_end_date { nil }

Expand Down
8 changes: 8 additions & 0 deletions spec/factories/certificate_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@
end
end
end

trait :exemption do
certificate_type_code { 'Y' }
end

trait :license do
certificate_type_code { 'L' }
end
end

factory :certificate_description_period do
Expand Down
32 changes: 19 additions & 13 deletions spec/factories/geographical_area_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,25 @@
sequence(:geographical_area_id) { |n| n }

factory :geographical_area do
transient do
members { [] }
end

geographical_area_sid { generate(:geographical_area_sid) }
geographical_area_id { Forgery(:basic).text(exactly: 2) }
validity_start_date { 3.years.ago.beginning_of_day }
validity_end_date { nil }

after(:create) do |geographical_area, evaluator|
Array.wrap(evaluator.members).each do |member|
create(
:geographical_area_membership,
geographical_area_group_sid: geographical_area.geographical_area_sid,
geographical_area_sid: member.geographical_area_sid,
)
end
end

trait :erga_omnes do
geographical_area_id { '1011' }
end
Expand Down Expand Up @@ -51,19 +65,11 @@
end

trait :with_members do
after(:create) do |geographical_area, _evaluator|
member = create(
:geographical_area,
:with_description,
:country,
geographical_area_id: 'RO',
)

create(
:geographical_area_membership,
geographical_area_group_sid: geographical_area.geographical_area_sid,
geographical_area_sid: member.geographical_area_sid,
)
members do
create_list(:geographical_area, 1,
:with_description,
:country,
geographical_area_id: 'RO')
end
end

Expand Down
Loading

0 comments on commit 580dfcc

Please sign in to comment.