diff --git a/lib/mindee/parsing/v2/inference.rb b/lib/mindee/parsing/v2/inference.rb index ca5f815d..b462fc51 100644 --- a/lib/mindee/parsing/v2/inference.rb +++ b/lib/mindee/parsing/v2/inference.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require_relative 'inference_job' require_relative 'inference_model' require_relative 'inference_file' require_relative 'inference_result' @@ -12,6 +13,8 @@ module V2 class Inference # @return [String] Identifier of the inference (when provided by API). attr_reader :id + # @return [InferenceJob] Metadata about the job. + attr_reader :job # @return [InferenceModel] Information about the model used. attr_reader :model # @return [InferenceFile] Information about the processed file. @@ -26,6 +29,7 @@ def initialize(server_response) raise ArgumentError, 'server_response must be a Hash' unless server_response.is_a?(Hash) @model = InferenceModel.new(server_response['model']) + @job = InferenceJob.new(server_response['job']) if server_response.key?('job') @file = InferenceFile.new(server_response['file']) @active_options = InferenceActiveOptions.new(server_response['active_options']) @result = InferenceResult.new(server_response['result']) @@ -39,6 +43,7 @@ def to_s [ 'Inference', '#########', + @job.to_s, @model.to_s, @file.to_s, @active_options.to_s, diff --git a/lib/mindee/parsing/v2/inference_job.rb b/lib/mindee/parsing/v2/inference_job.rb new file mode 100644 index 00000000..e6b6f4b6 --- /dev/null +++ b/lib/mindee/parsing/v2/inference_job.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require_relative 'common_response' +require_relative 'job' + +module Mindee + module Parsing + module V2 + # HTTP response wrapper that embeds a V2 job. + class InferenceJob + # @return [String] UUID of the Job. + attr_reader :id + + def initialize(server_response) + @id = server_response['id'] + end + + # @return [String] String representation of the job. + def to_s + "Job\n===\n:ID: #{@id}\n" + end + end + end + end +end diff --git a/lib/mindee/parsing/v2/job.rb b/lib/mindee/parsing/v2/job.rb index 5e6d9aab..3185397f 100644 --- a/lib/mindee/parsing/v2/job.rb +++ b/lib/mindee/parsing/v2/job.rb @@ -11,8 +11,10 @@ module V2 class Job # @return [String] Unique job identifier. attr_reader :id - # @return [DateTime, nil] Timestamp of creation. + # @return [Time] Date and time of the Job creation. attr_reader :created_at + # @return [Time, nil] Date and time of the Job completion. Filled once processing is finished. + attr_reader :completed_at # @return [String] Identifier of the model used. attr_reader :model_id # @return [String] Name of the processed file. @@ -30,6 +32,7 @@ class Job # @return [ErrorResponse, nil] Error details when the job failed. attr_reader :error + # rubocop:disable Metrics/CyclomaticComplexity # @param server_response [Hash] Parsed JSON payload from the API. def initialize(server_response) raise ArgumentError, 'server_response must be a Hash' unless server_response.is_a?(Hash) @@ -39,7 +42,10 @@ def initialize(server_response) unless server_response['error'].nil? || server_response['error'].empty? @error = ErrorResponse.new(server_response['error']) end - @created_at = Time.iso8601(server_response['created_at']) + @created_at = Time.iso8601(server_response['created_at']) + if server_response.key?('completed_at') && !server_response['completed_at'].nil? + @completed_at = Time.iso8601(server_response['completed_at']) + end @model_id = server_response['model_id'] @polling_url = server_response['polling_url'] @filename = server_response['filename'] @@ -50,6 +56,7 @@ def initialize(server_response) @webhooks.push JobWebhook.new(webhook) end end + # rubocop:enable Metrics/CyclomaticComplexity # String representation. # @return [String] diff --git a/sig/mindee/parsing/v2/inference.rbs b/sig/mindee/parsing/v2/inference.rbs index bc11c212..cbdc794c 100644 --- a/sig/mindee/parsing/v2/inference.rbs +++ b/sig/mindee/parsing/v2/inference.rbs @@ -3,7 +3,9 @@ module Mindee module Parsing module V2 class Inference + attr_reader id: String + attr_reader job: InferenceJob attr_reader model: InferenceModel attr_reader file: InferenceFile attr_reader active_options: InferenceActiveOptions diff --git a/sig/mindee/parsing/v2/inference_job.rbs b/sig/mindee/parsing/v2/inference_job.rbs new file mode 100644 index 00000000..02957681 --- /dev/null +++ b/sig/mindee/parsing/v2/inference_job.rbs @@ -0,0 +1,12 @@ +module Mindee + module Parsing + module V2 + class InferenceJob + attr_reader id: String + + def initialize: (Hash[String | Symbol, untyped]) -> void + def to_s: -> String + end + end + end +end diff --git a/sig/mindee/parsing/v2/job.rbs b/sig/mindee/parsing/v2/job.rbs index 7f4afbe0..1b60d013 100644 --- a/sig/mindee/parsing/v2/job.rbs +++ b/sig/mindee/parsing/v2/job.rbs @@ -4,7 +4,8 @@ module Mindee module V2 class Job attr_reader alias: String - attr_reader created_at: DateTime? + attr_reader created_at: Time + attr_reader completed_at: Time? attr_reader error: ErrorResponse? attr_reader filename: String attr_reader id: String diff --git a/spec/data b/spec/data index 67ccc1d9..c2e36f5b 160000 --- a/spec/data +++ b/spec/data @@ -1 +1 @@ -Subproject commit 67ccc1d9cf8b6263860f79eafbaa2e8b8dd7ac3f +Subproject commit c2e36f5b635386cb9bb922b517c4e02039b0a122 diff --git a/spec/v2/client_v2_integration.rb b/spec/v2/client_v2_integration.rb index 6be430bf..d5da7f10 100644 --- a/spec/v2/client_v2_integration.rb +++ b/spec/v2/client_v2_integration.rb @@ -219,7 +219,8 @@ context 'A Data Schema Override' do it 'Overrides successfully' do - data_schema_replace = File.read(File.join(V2_DATA_DIR, 'inference', 'data_schema_replace_param.json')) + data_schema_replace = File.read(File.join(V2_DATA_DIR, 'products', 'extraction', + 'data_schema_replace_param.json')) input = Mindee::Input::Source::PathInputSource.new(File.join(FILE_TYPES_DIR, 'pdf', 'blank_1.pdf')) inference_params = Mindee::Input::InferenceParameters.new( diff --git a/spec/v2/client_v2_spec.rb b/spec/v2/client_v2_spec.rb index 8538fd60..dc1b3714 100644 --- a/spec/v2/client_v2_spec.rb +++ b/spec/v2/client_v2_spec.rb @@ -89,6 +89,29 @@ def stub_next_request_with(method, hash:, status_code: 0) resp = client.get_job('123e4567-e89b-12d3-a456-426614174000') expect(resp).to be_a(Mindee::Parsing::V2::JobResponse) expect(resp.job.status).to eq('Processing') + expect( + resp.job.created_at.strftime('%Y-%m-%dT%H:%M:%S.%6N') + ).to eq('2025-07-03T14:27:58.974451') + expect(resp.job.completed_at).to be_nil + end + + it 'should deserialize a job properly' do + json_path = File.join(V2_DATA_DIR, 'job', 'ok_processed_webhooks_ok.json') + parsed = File.read(json_path) + stub_next_request_with(:inference_job_req_get, hash: parsed, status_code: 200) + + resp = client.get_job('123e4567-e89b-12d3-a456-426614174000') + expect(resp).to be_a(Mindee::Parsing::V2::JobResponse) + expect(resp.job.status).to eq('Processed') + expect(resp.job.model_id).to eq('87654321-4321-4321-4321-CBA987654321') + expect(resp.job.filename).to eq('default_sample.jpg') + expect(resp.job.alias).to eq('dummy-alias.jpg') + expect( + resp.job.created_at.strftime('%Y-%m-%dT%H:%M:%S.%6N') + ).to eq('2026-04-20T18:27:58.974451') + expect( + resp.job.completed_at.strftime('%Y-%m-%dT%H:%M:%S.%6N') + ).to eq('2026-04-20T18:32:02.734312') end ENV.delete('MINDEE_V2_BASE_URL') end diff --git a/spec/v2/input/inference_parameter_spec.rb b/spec/v2/input/inference_parameter_spec.rb index f3e10a6d..af0ccd6a 100644 --- a/spec/v2/input/inference_parameter_spec.rb +++ b/spec/v2/input/inference_parameter_spec.rb @@ -4,7 +4,9 @@ require 'mindee/input/data_schema' describe Mindee::Input::InferenceParameters do - let(:extracted_schema_content) { File.read(File.join(V2_DATA_DIR, 'inference', 'data_schema_replace_param.json')) } + let(:extracted_schema_content) do + File.read(File.join(V2_DATA_DIR, 'products', 'extraction', 'data_schema_replace_param.json')) + end let(:extracted_schema_hash) { JSON.parse(extracted_schema_content) } let(:extracted_schema_str) { extracted_schema_hash.to_json } let(:extracted_schema_object) { Mindee::Input::DataSchema.new(extracted_schema_hash) } diff --git a/spec/v2/input/local_response_v2_spec.rb b/spec/v2/input/local_response_v2_spec.rb index b39a8fb2..f27a5961 100644 --- a/spec/v2/input/local_response_v2_spec.rb +++ b/spec/v2/input/local_response_v2_spec.rb @@ -5,7 +5,7 @@ def assert_local_response(local_response) dummy_secret_key = 'ogNjY44MhvKPGTtVsI8zG82JqWQa68woYQH' - signature = 'f390d9f7f57ac04f47b6309d8a40236b0182610804fc20e91b1f6028aaca07a7' + signature = 'e51bdf80f1a08ed44ee161100fc30a25cb35b4ede671b0a575dc9064a3f5dbf1' expect(local_response.file).to_not be(nil) expect(local_response.valid_hmac_signature?( dummy_secret_key, 'invalid signature' @@ -18,7 +18,7 @@ def assert_local_response(local_response) end describe Mindee::Input::LocalResponse do - let(:file_path) { File.join(V2_DATA_DIR, 'inference', 'standard_field_types.json') } + let(:file_path) { File.join(V2_DATA_DIR, 'products', 'extraction', 'standard_field_types.json') } context 'A V2 local response' do it 'should load from a path' do response = Mindee::Input::LocalResponse.new(file_path) diff --git a/spec/v2/parsing/inference_spec.rb b/spec/v2/parsing/inference_spec.rb index 1be2dd39..f0c496c6 100644 --- a/spec/v2/parsing/inference_spec.rb +++ b/spec/v2/parsing/inference_spec.rb @@ -3,19 +3,19 @@ require 'mindee' RSpec.describe 'inference' do - let(:findoc_path) { File.join(V2_DATA_DIR, 'products', 'financial_document') } - let(:inference_path) { File.join(V2_DATA_DIR, 'inference') } - let(:deep_nested_field_path) { File.join(inference_path, 'deep_nested_fields.json') } - let(:standard_field_path) { File.join(inference_path, 'standard_field_types.json') } - let(:standard_field_rst_path) { File.join(inference_path, 'standard_field_types.rst') } + let(:findoc_path) { File.join(V2_DATA_DIR, 'products', 'extraction', 'financial_document') } + let(:extraction_path) { File.join(V2_DATA_DIR, 'products', 'extraction') } + let(:deep_nested_field_path) { File.join(extraction_path, 'deep_nested_fields.json') } + let(:standard_field_path) { File.join(extraction_path, 'standard_field_types.json') } + let(:standard_field_rst_path) { File.join(extraction_path, 'standard_field_types.rst') } let(:location_field_path) { File.join(findoc_path, 'complete_with_coordinates.json') } - let(:raw_text_json_path) { File.join(inference_path, 'raw_texts.json') } - let(:raw_text_str_path) { File.join(inference_path, 'raw_texts.txt') } + let(:raw_text_json_path) { File.join(extraction_path, 'raw_texts.json') } + let(:raw_text_str_path) { File.join(extraction_path, 'raw_texts.txt') } let(:blank_path) { File.join(findoc_path, 'blank.json') } let(:complete_path) { File.join(findoc_path, 'complete.json') } - let(:rag_matched_path) { File.join(inference_path, 'rag_matched.json') } - let(:rag_not_matched_path) { File.join(inference_path, 'rag_not_matched.json') } - let(:text_context_path) { File.join(inference_path, 'text_context_enabled.json') } + let(:rag_matched_path) { File.join(extraction_path, 'rag_matched.json') } + let(:rag_not_matched_path) { File.join(extraction_path, 'rag_not_matched.json') } + let(:text_context_path) { File.join(extraction_path, 'text_context_enabled.json') } def load_v2_inference(resource_path) local_response = Mindee::Input::LocalResponse.new(resource_path) @@ -62,6 +62,10 @@ def load_v2_inference(resource_path) it 'loads a complete inference with valid properties' do response = load_v2_inference(complete_path) inference = response.inference + job = inference.job + expect(job).not_to be_nil + expect(job).to be_a(Mindee::Parsing::V2::InferenceJob) + expect(job.id).to eq('12345678-1234-1234-1234-jobid1234567') expect(inference).not_to be_nil expect(inference.id).to eq('12345678-1234-1234-1234-123456789abc')