diff --git a/lib/tasks/payments.rake b/lib/tasks/payments.rake
new file mode 100644
index 000000000..03eb28809
--- /dev/null
+++ b/lib/tasks/payments.rake
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+OUTSIDE_CONTRACT = [
+  %w[0382170C 2472000831],
+  %w[0690652J 2472340732],
+  %w[0690652J 2472340733],
+  %w[0690652J 2412010122],
+  %w[0690652J 2412344721],
+  %w[0690652J 2412344722],
+  %w[0690652J 2412521921],
+  %w[0690652J 2412521922],
+  %w[0690652J 2412543422],
+  %w[0691875N 2473360333],
+  %w[0691875N 2473360531],
+  %w[0691875N 2473360632],
+  %w[0691875N 2413361521],
+  %w[0691875N 2413361522],
+  %w[0442083A 2473121131],
+  %w[0442083A 2473121332],
+  %w[0442083A 2473121333],
+  %w[0442227G 2403320511],
+  %w[0910838S 2473000433],
+  %w[0910838S 2473121432]
+].freeze
+
+PRIVATE_HEALTH_ESTABLISHMENTS = %w[0541769E 0010212A 0930075B].freeze
+
+def select_perfect_pfmps
+  perfect = []
+
+  Pfmp
+    .joins(Pfmp.most_recent_transition_join)
+    .joins(:payment_requests, :schooling, :establishment, student: :rib)
+    .merge(Student.asp_ready)
+    .order(end_date: :asc)
+    .merge(Schooling.with_attributive_decisions)
+    .where.not("establishments.uai": PRIVATE_HEALTH_ESTABLISHMENTS)
+    .in_state(:validated)
+    .preload(:schooling, student: :rib)
+    .find_in_batches
+    .with_index do |pfmps, index|
+    puts "looking for perfect PFMPs in batch number #{index} (have #{perfect.count} perfect PFMPs so far)"
+
+    ready = pfmps
+            .select(&:valid?)
+            .each   { |pfmp| grab_missing_status(pfmp) }
+            .reject { |pfmp| outside_contract?(pfmp) }
+            .reject { |pfmp| duplicates?(pfmp) }
+            .reject { |pfmp| needs_abrogated_da?(pfmp) }
+            .select { |pfmp| pfmp.payment_requests.last.can_transition_to?(:ready) }
+
+    perfect.concat(ready)
+
+    break if perfect.count > 1000
+  end
+
+  puts "all good! pfmps: #{perfect.count}"
+end
+
+def outside_contract?(pfmp)
+  OUTSIDE_CONTRACT.any? { |uai, mef| pfmp.establishment.uai == uai && pfmp.mef.code == mef }
+end
+
+def grab_missing_status(pfmp)
+  return if pfmp.schooling.status.present?
+
+  puts "fetching student information again..."
+  FetchStudentInformationJob.perform_now(pfmp.schooling)
+  puts "done."
+end
+
+def duplicates?(pfmp)
+  pfmp.student.pfmps.excluding(pfmp).any? do |other|
+    other.start_date == pfmp.start_date && other.end_date == pfmp.end_date
+  end
+end
+
+def needs_abrogated_da?(pfmp)
+  pfmp.student.pfmps.joins(:mef).map(&:mef).uniq.count > 1 &&
+    pfmp.student.schoolings.joins(:classe).map(&:establishment).uniq.count > 1
+end