Skip to content

Commit 23d2783

Browse files
new script (#226)
* new script * lint --------- Co-authored-by: Drew Proebstel <dproebstel@codeforamerica.org>
1 parent 1587c9a commit 23d2783

1 file changed

Lines changed: 166 additions & 0 deletions

File tree

script/attach_submission_pdfs_2.rb

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
#!/usr/bin/env ruby
2+
require_relative "../config/environment"
3+
require "json"
4+
require "stringio"
5+
6+
class AttachSubmissionPdfs < Thor
7+
default_task :attach
8+
9+
# Usage:
10+
# bundle exec ruby script/attach_submission_pdfs.rb attach MAPPING_JSON_KEY BUCKET
11+
desc "attach MAPPING_JSON_KEY BUCKET", "Attach PDFs from S3 and write results to the same bucket"
12+
def attach(mapping_json_key, bucket)
13+
@bucket = bucket
14+
@mapping_json_key = mapping_json_key
15+
16+
# Load mapping JSON
17+
mappings = load_mappings_from_s3(@mapping_json_key)
18+
mappings_by_id = mappings.transform_keys { |id_str| id_str.to_i }
19+
20+
total = mappings_by_id.size
21+
puts "Loaded #{total} mappings from #{@bucket}/#{@mapping_json_key}"
22+
23+
# Build results filename
24+
timestamp = Time.current.strftime("%Y%m%d-%H%M%S")
25+
base_name = File.basename(@mapping_json_key, File.extname(@mapping_json_key))
26+
results_key = "#{base_name}-attachment-results-#{timestamp}.json"
27+
28+
# Results structure
29+
results = {
30+
meta: {
31+
mapping_key: @mapping_json_key,
32+
bucket: @bucket,
33+
total: total,
34+
processed: 0
35+
},
36+
attached: [], # [{ intake_id:, key: }]
37+
already_attached: [], # [{ intake_id: }]
38+
missing: [], # [{ intake_id:, key: }]
39+
errors: [] # [{ intake_id:, error_class:, message: }]
40+
}
41+
42+
batch_size = 500
43+
flush_interval = 500
44+
45+
attached_count = 0
46+
already_attached_count = 0
47+
missing_count = 0
48+
49+
# Batch DB fetch with preloaded attachments
50+
StateFileArchivedIntake
51+
.where(id: mappings_by_id.keys)
52+
.includes(submission_pdf_attachment: :blob)
53+
.find_each(batch_size: batch_size) do |intake|
54+
intake_id = intake.id
55+
s3_key = mappings_by_id[intake_id]
56+
57+
unless s3_key
58+
results[:errors] << {
59+
intake_id: intake_id,
60+
error_class: "MissingMapping",
61+
message: "Mapping JSON did not contain an entry for this intake"
62+
}
63+
next
64+
end
65+
66+
if intake.submission_pdf.attached?
67+
already_attached_count += 1
68+
results[:already_attached] << {intake_id: intake_id}
69+
next
70+
end
71+
72+
begin
73+
io = pdf_io_from_s3(s3_key)
74+
75+
intake.submission_pdf.attach(
76+
io: io,
77+
filename: "#{File.basename(s3_key)}.pdf",
78+
content_type: "application/pdf"
79+
)
80+
81+
attached_count += 1
82+
results[:attached] << {intake_id: intake_id, key: s3_key}
83+
rescue Aws::S3::Errors::NoSuchKey
84+
missing_count += 1
85+
results[:missing] << {intake_id: intake_id, key: s3_key}
86+
rescue => e
87+
results[:errors] << {
88+
intake_id: intake_id,
89+
error_class: e.class.to_s,
90+
message: e.message
91+
}
92+
ensure
93+
# Progress tracking
94+
results[:meta][:processed] += 1
95+
processed = results[:meta][:processed]
96+
97+
if (processed % flush_interval).zero?
98+
upload_results(results_key, results)
99+
puts "Processed #{processed}/#{total}..."
100+
end
101+
end
102+
end
103+
104+
# Final upload
105+
upload_results(results_key, results)
106+
107+
puts "---------------------------------------"
108+
puts "Finished attaching PDFs"
109+
puts "Total mapped: #{total}"
110+
puts "Successfully attached: #{attached_count}"
111+
puts "Already attached: #{already_attached_count}"
112+
puts "Missing S3 keys: #{missing_count}"
113+
puts "Results JSON written at: #{@bucket}/#{results_key}"
114+
puts "---------------------------------------"
115+
end
116+
117+
no_tasks do
118+
def load_mappings_from_s3(key)
119+
body = s3_client.get_object(
120+
bucket: @bucket,
121+
key: key
122+
).body.read
123+
124+
json = JSON.parse(body)
125+
126+
# If JSON was enriched before, use its "mappings"
127+
json.key?("mappings") ? json["mappings"] : json
128+
end
129+
130+
def pdf_io_from_s3(key)
131+
body = s3_client.get_object(
132+
bucket: @bucket,
133+
key: key
134+
).body.read
135+
StringIO.new(body)
136+
end
137+
138+
def upload_results(key, structure)
139+
json = JSON.pretty_generate(structure)
140+
141+
s3_client.put_object(
142+
bucket: @bucket,
143+
key: key,
144+
body: json
145+
)
146+
end
147+
148+
def s3_client
149+
if ENV["AWS_ACCESS_KEY_ID"].present? &&
150+
ENV["AWS_SECRET_ACCESS_KEY"].present?
151+
Aws::S3::Client.new(
152+
region: "us-east-1",
153+
credentials: Aws::Credentials.new(
154+
ENV["AWS_ACCESS_KEY_ID"],
155+
ENV["AWS_SECRET_ACCESS_KEY"],
156+
ENV["AWS_SESSION_TOKEN"]
157+
)
158+
)
159+
else
160+
Aws::S3::Client.new(region: "us-east-1")
161+
end
162+
end
163+
end
164+
end
165+
166+
AttachSubmissionPdfs.start

0 commit comments

Comments
 (0)