carddb 0.3.15 → 0.4.0
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 +4 -4
- data/.rspec_status +131 -118
- data/CHANGELOG.md +3 -0
- data/README.md +134 -0
- data/examples/scan_workflow.rb +49 -0
- data/lib/carddb/client.rb +14 -0
- data/lib/carddb/collection.rb +261 -0
- data/lib/carddb/query_builder.rb +309 -0
- data/lib/carddb/resources/files.rb +75 -0
- data/lib/carddb/resources/games.rb +19 -0
- data/lib/carddb/resources/scan_templates.rb +128 -0
- data/lib/carddb/resources/scans.rb +133 -0
- data/lib/carddb/version.rb +1 -1
- data/lib/carddb.rb +16 -0
- metadata +4 -1
data/lib/carddb/collection.rb
CHANGED
|
@@ -409,6 +409,16 @@ module CardDB
|
|
|
409
409
|
end
|
|
410
410
|
end
|
|
411
411
|
|
|
412
|
+
# Fetch publisher-defined scan regions for this game.
|
|
413
|
+
def scan_regions(include_inactive: nil)
|
|
414
|
+
raise Error, 'No client available to fetch scan regions' unless client
|
|
415
|
+
|
|
416
|
+
publisher_slug = publisher&.[]('slug')
|
|
417
|
+
raise Error, 'Publisher slug not available on this game' unless publisher_slug
|
|
418
|
+
|
|
419
|
+
client.games.get_regions(publisher_slug: publisher_slug, game_key: key, include_inactive: include_inactive)
|
|
420
|
+
end
|
|
421
|
+
|
|
412
422
|
private
|
|
413
423
|
|
|
414
424
|
def fetch_datasets(purpose: nil, search: nil, first: nil, after: nil)
|
|
@@ -932,6 +942,257 @@ module CardDB
|
|
|
932
942
|
end
|
|
933
943
|
end
|
|
934
944
|
|
|
945
|
+
class ScanJobPayload < Resource
|
|
946
|
+
def created? = !!data['created']
|
|
947
|
+
|
|
948
|
+
def job
|
|
949
|
+
@job ||= data['job'] ? ScanJob.new(data['job'], client: client) : nil
|
|
950
|
+
end
|
|
951
|
+
end
|
|
952
|
+
|
|
953
|
+
class ScanBestMatch < Resource
|
|
954
|
+
def record_id = data['recordId']
|
|
955
|
+
def score = data['score']
|
|
956
|
+
def confidence = data['confidence']
|
|
957
|
+
end
|
|
958
|
+
|
|
959
|
+
class ScanCandidate < Resource
|
|
960
|
+
def record_id = data['recordId']
|
|
961
|
+
def rank = data['rank']
|
|
962
|
+
def score = data['score']
|
|
963
|
+
def confidence = data['confidence']
|
|
964
|
+
def reasons = data['reasons'] || []
|
|
965
|
+
end
|
|
966
|
+
|
|
967
|
+
class ScanJobError < Resource
|
|
968
|
+
def code = data['code']
|
|
969
|
+
def message = data['message']
|
|
970
|
+
end
|
|
971
|
+
|
|
972
|
+
class ScanWebhookState < Resource
|
|
973
|
+
def configured? = !!data['configured']
|
|
974
|
+
def url = data['url']
|
|
975
|
+
def latest_delivery_id = data['latestDeliveryId']
|
|
976
|
+
def status = data['status']
|
|
977
|
+
def attempt_count = data['attemptCount']
|
|
978
|
+
def next_attempt_at = parse_time(data['nextAttemptAt'])
|
|
979
|
+
def last_attempt_at = parse_time(data['lastAttemptAt'])
|
|
980
|
+
def delivered_at = parse_time(data['deliveredAt'])
|
|
981
|
+
def last_status_code = data['lastStatusCode']
|
|
982
|
+
def last_error = data['lastError']
|
|
983
|
+
end
|
|
984
|
+
|
|
985
|
+
class ScanStageMetrics < Resource
|
|
986
|
+
def name = data['name']
|
|
987
|
+
def status = data['status']
|
|
988
|
+
def duration_ms = data['durationMs']
|
|
989
|
+
def completed_at = parse_time(data['completedAt'])
|
|
990
|
+
def details = data['details'] || {}
|
|
991
|
+
end
|
|
992
|
+
|
|
993
|
+
class ScanJobMetrics < Resource
|
|
994
|
+
def feature_version = data['featureVersion']
|
|
995
|
+
def candidate_count = data['candidateCount']
|
|
996
|
+
def confidence_score = data['confidenceScore']
|
|
997
|
+
def confidence_label = data['confidenceLabel']
|
|
998
|
+
def timings = data['timings'] || {}
|
|
999
|
+
|
|
1000
|
+
def stages
|
|
1001
|
+
@stages ||= (data['stages'] || []).map { |stage| ScanStageMetrics.new(stage, client: client) }
|
|
1002
|
+
end
|
|
1003
|
+
end
|
|
1004
|
+
|
|
1005
|
+
class ScanJob < Resource
|
|
1006
|
+
def job_id = data['jobId']
|
|
1007
|
+
def status = data['status']
|
|
1008
|
+
def publisher_slug = data['publisherSlug']
|
|
1009
|
+
def game_key = data['gameKey']
|
|
1010
|
+
def dataset_key = data['datasetKey']
|
|
1011
|
+
def file_id = data['fileId']
|
|
1012
|
+
def client_request_id = data['clientRequestId']
|
|
1013
|
+
def template_id = data['templateId']
|
|
1014
|
+
def template_version_id = data['templateVersionId']
|
|
1015
|
+
def extracted = data['extracted'] || {}
|
|
1016
|
+
def created_at = parse_time(data['createdAt'])
|
|
1017
|
+
def updated_at = parse_time(data['updatedAt'])
|
|
1018
|
+
def started_at = parse_time(data['startedAt'])
|
|
1019
|
+
def completed_at = parse_time(data['completedAt'])
|
|
1020
|
+
def completed? = status.to_s.downcase == 'completed'
|
|
1021
|
+
def failed? = status.to_s.downcase == 'failed'
|
|
1022
|
+
def cancelled? = status.to_s.downcase == 'cancelled'
|
|
1023
|
+
|
|
1024
|
+
def best_match
|
|
1025
|
+
@best_match ||= data['bestMatch'] ? ScanBestMatch.new(data['bestMatch'], client: client) : nil
|
|
1026
|
+
end
|
|
1027
|
+
|
|
1028
|
+
def candidates
|
|
1029
|
+
@candidates ||= (data['candidates'] || []).map { |candidate| ScanCandidate.new(candidate, client: client) }
|
|
1030
|
+
end
|
|
1031
|
+
|
|
1032
|
+
def metrics
|
|
1033
|
+
@metrics ||= data['metrics'] ? ScanJobMetrics.new(data['metrics'], client: client) : nil
|
|
1034
|
+
end
|
|
1035
|
+
|
|
1036
|
+
def webhook
|
|
1037
|
+
@webhook ||= data['webhook'] ? ScanWebhookState.new(data['webhook'], client: client) : nil
|
|
1038
|
+
end
|
|
1039
|
+
|
|
1040
|
+
def error
|
|
1041
|
+
@error ||= data['error'] ? ScanJobError.new(data['error'], client: client) : nil
|
|
1042
|
+
end
|
|
1043
|
+
end
|
|
1044
|
+
|
|
1045
|
+
class ScanFeedbackPayload < Resource
|
|
1046
|
+
def feedback_id = data['feedbackId']
|
|
1047
|
+
def job_id = data['jobId']
|
|
1048
|
+
def status = data['status']
|
|
1049
|
+
def persisted? = !!data['persisted']
|
|
1050
|
+
def correct = data['correct']
|
|
1051
|
+
def predicted_record_id = data['predictedRecordId']
|
|
1052
|
+
def selected_record_id = data['selectedRecordId']
|
|
1053
|
+
def feature_version = data['featureVersion']
|
|
1054
|
+
def message = data['message']
|
|
1055
|
+
end
|
|
1056
|
+
|
|
1057
|
+
class ScanFeedbackMetrics < Resource
|
|
1058
|
+
def total = data['total']
|
|
1059
|
+
def correct = data['correct']
|
|
1060
|
+
def corrected = data['corrected']
|
|
1061
|
+
def unknown = data['unknown']
|
|
1062
|
+
def accuracy = data['accuracy']
|
|
1063
|
+
end
|
|
1064
|
+
|
|
1065
|
+
class ScanFeatureVersionMetrics < Resource
|
|
1066
|
+
def feature_version = data['featureVersion']
|
|
1067
|
+
def job_count = data['jobCount']
|
|
1068
|
+
def completed_jobs = data['completedJobs']
|
|
1069
|
+
def failed_jobs = data['failedJobs']
|
|
1070
|
+
def average_confidence = data['averageConfidence']
|
|
1071
|
+
def average_shortlist_size = data['averageShortlistSize']
|
|
1072
|
+
def feedback_count = data['feedbackCount']
|
|
1073
|
+
def correct_feedback = data['correctFeedback']
|
|
1074
|
+
def corrected_feedback = data['correctedFeedback']
|
|
1075
|
+
def feedback_accuracy = data['feedbackAccuracy']
|
|
1076
|
+
end
|
|
1077
|
+
|
|
1078
|
+
class ScanMetrics < Resource
|
|
1079
|
+
def publisher_slug = data['publisherSlug']
|
|
1080
|
+
def game_key = data['gameKey']
|
|
1081
|
+
def dataset_key = data['datasetKey']
|
|
1082
|
+
def since = parse_time(data['since'])
|
|
1083
|
+
def until = parse_time(data['until'])
|
|
1084
|
+
def generated_at = parse_time(data['generatedAt'])
|
|
1085
|
+
def total_jobs = data['totalJobs']
|
|
1086
|
+
def completed_jobs = data['completedJobs']
|
|
1087
|
+
def failed_jobs = data['failedJobs']
|
|
1088
|
+
def failure_rate = data['failureRate']
|
|
1089
|
+
def average_shortlist_size = data['averageShortlistSize']
|
|
1090
|
+
def confidence_distribution = data['confidenceDistribution'] || {}
|
|
1091
|
+
|
|
1092
|
+
def feedback
|
|
1093
|
+
@feedback ||= data['feedback'] ? ScanFeedbackMetrics.new(data['feedback'], client: client) : nil
|
|
1094
|
+
end
|
|
1095
|
+
|
|
1096
|
+
def feature_versions
|
|
1097
|
+
@feature_versions ||= (data['featureVersions'] || []).map do |version|
|
|
1098
|
+
ScanFeatureVersionMetrics.new(version, client: client)
|
|
1099
|
+
end
|
|
1100
|
+
end
|
|
1101
|
+
end
|
|
1102
|
+
|
|
1103
|
+
class ScanTemplateWarning < Resource
|
|
1104
|
+
def code = data['code']
|
|
1105
|
+
def region = data['region']
|
|
1106
|
+
def message = data['message']
|
|
1107
|
+
end
|
|
1108
|
+
|
|
1109
|
+
class ScanTemplateRegion < Resource
|
|
1110
|
+
def id = data['id']
|
|
1111
|
+
def key = data['key']
|
|
1112
|
+
def label = data['label']
|
|
1113
|
+
def sort_order = data['sortOrder']
|
|
1114
|
+
def shape_type = data['shapeType']
|
|
1115
|
+
def geometry = data['geometry'] || {}
|
|
1116
|
+
def semantic_type = data['semanticType']
|
|
1117
|
+
def extraction_mode = data['extractionMode']
|
|
1118
|
+
def lookup_mode = data['lookupMode']
|
|
1119
|
+
def required? = !!data['isRequired']
|
|
1120
|
+
def weight = data['weight']
|
|
1121
|
+
def config = data['config'] || {}
|
|
1122
|
+
end
|
|
1123
|
+
|
|
1124
|
+
class ScanTemplate < Resource
|
|
1125
|
+
def id = data['id']
|
|
1126
|
+
def key = data['key']
|
|
1127
|
+
def name = data['name']
|
|
1128
|
+
def status = data['status']
|
|
1129
|
+
def version = data['version']
|
|
1130
|
+
def default? = !!data['isDefault']
|
|
1131
|
+
def publisher_slug = data['publisherSlug']
|
|
1132
|
+
def game_key = data['gameKey']
|
|
1133
|
+
def dataset_key = data['datasetKey']
|
|
1134
|
+
def example_file_id = data['exampleFileId']
|
|
1135
|
+
def coordinate_space = data['coordinateSpace']
|
|
1136
|
+
def card_aspect_ratio = data['cardAspectRatio']
|
|
1137
|
+
def notes = data['notes']
|
|
1138
|
+
def config = data['config'] || {}
|
|
1139
|
+
def created_at = parse_time(data['createdAt'])
|
|
1140
|
+
def updated_at = parse_time(data['updatedAt'])
|
|
1141
|
+
|
|
1142
|
+
def regions
|
|
1143
|
+
@regions ||= (data['regions'] || []).map { |region| ScanTemplateRegion.new(region, client: client) }
|
|
1144
|
+
end
|
|
1145
|
+
end
|
|
1146
|
+
|
|
1147
|
+
class ScanTemplateResolution < Resource
|
|
1148
|
+
def template
|
|
1149
|
+
@template ||= data['template'] ? ScanTemplate.new(data['template'], client: client) : nil
|
|
1150
|
+
end
|
|
1151
|
+
|
|
1152
|
+
def warnings
|
|
1153
|
+
@warnings ||= (data['warnings'] || []).map { |warning| ScanTemplateWarning.new(warning, client: client) }
|
|
1154
|
+
end
|
|
1155
|
+
end
|
|
1156
|
+
|
|
1157
|
+
class ScanTemplatePayload < ScanTemplateResolution; end
|
|
1158
|
+
|
|
1159
|
+
class ScanTemplateRegionPreview < Resource
|
|
1160
|
+
def key = data['key']
|
|
1161
|
+
def label = data['label']
|
|
1162
|
+
def semantic_type = data['semanticType']
|
|
1163
|
+
def extraction_mode = data['extractionMode']
|
|
1164
|
+
def lookup_mode = data['lookupMode']
|
|
1165
|
+
def raw_text = data['rawText']
|
|
1166
|
+
def normalized_value = data['normalizedValue']
|
|
1167
|
+
def source = data['source']
|
|
1168
|
+
def status = data['status']
|
|
1169
|
+
def confidence = data['confidence']
|
|
1170
|
+
end
|
|
1171
|
+
|
|
1172
|
+
class ScanTemplatePreview < Resource
|
|
1173
|
+
def message = data['message']
|
|
1174
|
+
|
|
1175
|
+
def warnings
|
|
1176
|
+
@warnings ||= (data['warnings'] || []).map { |warning| ScanTemplateWarning.new(warning, client: client) }
|
|
1177
|
+
end
|
|
1178
|
+
|
|
1179
|
+
def regions
|
|
1180
|
+
@regions ||= (data['regions'] || []).map { |region| ScanTemplateRegionPreview.new(region, client: client) }
|
|
1181
|
+
end
|
|
1182
|
+
end
|
|
1183
|
+
|
|
1184
|
+
class GameScanRegion < Resource
|
|
1185
|
+
def id = data['id']
|
|
1186
|
+
def key = data['key']
|
|
1187
|
+
def name = data['name']
|
|
1188
|
+
def description = data['description']
|
|
1189
|
+
def sort_order = data['sortOrder']
|
|
1190
|
+
def active? = !!data['isActive']
|
|
1191
|
+
def metadata = data['metadata'] || {}
|
|
1192
|
+
def created_at = parse_time(data['createdAt'])
|
|
1193
|
+
def updated_at = parse_time(data['updatedAt'])
|
|
1194
|
+
end
|
|
1195
|
+
|
|
935
1196
|
class ObjectField < Resource
|
|
936
1197
|
def id = data['id']
|
|
937
1198
|
def object_type_id = data['objectTypeId']
|
data/lib/carddb/query_builder.rb
CHANGED
|
@@ -680,6 +680,140 @@ module CardDB
|
|
|
680
680
|
GRAPHQL
|
|
681
681
|
end
|
|
682
682
|
|
|
683
|
+
def create_scan_job
|
|
684
|
+
<<~GRAPHQL
|
|
685
|
+
mutation CreateScanJob($input: CreateScanJobInput!) {
|
|
686
|
+
createScanJob(input: $input) {
|
|
687
|
+
job {
|
|
688
|
+
#{scan_job_fields}
|
|
689
|
+
}
|
|
690
|
+
created
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
GRAPHQL
|
|
694
|
+
end
|
|
695
|
+
|
|
696
|
+
def scan_job
|
|
697
|
+
<<~GRAPHQL
|
|
698
|
+
query ScanJob($id: UUID!) {
|
|
699
|
+
scanJob(id: $id) {
|
|
700
|
+
#{scan_job_fields}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
GRAPHQL
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
def submit_scan_feedback
|
|
707
|
+
<<~GRAPHQL
|
|
708
|
+
mutation SubmitScanFeedback($input: SubmitScanFeedbackInput!) {
|
|
709
|
+
submitScanFeedback(input: $input) {
|
|
710
|
+
feedbackId
|
|
711
|
+
jobId
|
|
712
|
+
status
|
|
713
|
+
persisted
|
|
714
|
+
correct
|
|
715
|
+
predictedRecordId
|
|
716
|
+
selectedRecordId
|
|
717
|
+
featureVersion
|
|
718
|
+
message
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
GRAPHQL
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
def scan_metrics
|
|
725
|
+
<<~GRAPHQL
|
|
726
|
+
query ScanMetrics($input: ScanMetricsInput!) {
|
|
727
|
+
scanMetrics(input: $input) {
|
|
728
|
+
#{scan_metrics_fields}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
GRAPHQL
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
def resolve_scan_template
|
|
735
|
+
<<~GRAPHQL
|
|
736
|
+
query ResolveScanTemplate($input: ResolveScanTemplateInput!) {
|
|
737
|
+
resolveScanTemplate(input: $input) {
|
|
738
|
+
template {
|
|
739
|
+
#{scan_template_fields}
|
|
740
|
+
}
|
|
741
|
+
warnings {
|
|
742
|
+
#{scan_template_warning_fields}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
GRAPHQL
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
def scan_templates
|
|
750
|
+
<<~GRAPHQL
|
|
751
|
+
query ScanTemplates($input: ScanTemplatesInput!) {
|
|
752
|
+
scanTemplates(input: $input) {
|
|
753
|
+
templates {
|
|
754
|
+
#{scan_template_fields}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
GRAPHQL
|
|
759
|
+
end
|
|
760
|
+
|
|
761
|
+
def create_scan_template
|
|
762
|
+
<<~GRAPHQL
|
|
763
|
+
mutation CreateScanTemplate($input: SaveScanTemplateInput!) {
|
|
764
|
+
createScanTemplate(input: $input) {
|
|
765
|
+
template {
|
|
766
|
+
#{scan_template_fields}
|
|
767
|
+
}
|
|
768
|
+
warnings {
|
|
769
|
+
#{scan_template_warning_fields}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
GRAPHQL
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
def update_scan_template
|
|
777
|
+
<<~GRAPHQL
|
|
778
|
+
mutation UpdateScanTemplate($id: UUID!, $input: SaveScanTemplateInput!) {
|
|
779
|
+
updateScanTemplate(id: $id, input: $input) {
|
|
780
|
+
template {
|
|
781
|
+
#{scan_template_fields}
|
|
782
|
+
}
|
|
783
|
+
warnings {
|
|
784
|
+
#{scan_template_warning_fields}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
GRAPHQL
|
|
789
|
+
end
|
|
790
|
+
|
|
791
|
+
def preview_scan_template
|
|
792
|
+
<<~GRAPHQL
|
|
793
|
+
mutation PreviewScanTemplate($input: PreviewScanTemplateInput!) {
|
|
794
|
+
previewScanTemplate(input: $input) {
|
|
795
|
+
warnings {
|
|
796
|
+
#{scan_template_warning_fields}
|
|
797
|
+
}
|
|
798
|
+
regions {
|
|
799
|
+
#{scan_template_preview_region_fields}
|
|
800
|
+
}
|
|
801
|
+
message
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
GRAPHQL
|
|
805
|
+
end
|
|
806
|
+
|
|
807
|
+
def game_scan_regions
|
|
808
|
+
<<~GRAPHQL
|
|
809
|
+
query GameScanRegions($publisherSlug: String!, $gameKey: String!, $includeInactive: Boolean) {
|
|
810
|
+
gameScanRegions(publisherSlug: $publisherSlug, gameKey: $gameKey, includeInactive: $includeInactive) {
|
|
811
|
+
#{game_scan_region_fields}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
GRAPHQL
|
|
815
|
+
end
|
|
816
|
+
|
|
683
817
|
def export_job
|
|
684
818
|
<<~GRAPHQL
|
|
685
819
|
query ExportJob($id: UUID!) {
|
|
@@ -2252,6 +2386,181 @@ module CardDB
|
|
|
2252
2386
|
FIELDS
|
|
2253
2387
|
end
|
|
2254
2388
|
|
|
2389
|
+
def scan_job_fields
|
|
2390
|
+
<<~FIELDS
|
|
2391
|
+
jobId
|
|
2392
|
+
status
|
|
2393
|
+
publisherSlug
|
|
2394
|
+
gameKey
|
|
2395
|
+
datasetKey
|
|
2396
|
+
fileId
|
|
2397
|
+
clientRequestId
|
|
2398
|
+
templateId
|
|
2399
|
+
templateVersionId
|
|
2400
|
+
bestMatch {
|
|
2401
|
+
recordId
|
|
2402
|
+
score
|
|
2403
|
+
confidence
|
|
2404
|
+
}
|
|
2405
|
+
candidates {
|
|
2406
|
+
recordId
|
|
2407
|
+
rank
|
|
2408
|
+
score
|
|
2409
|
+
confidence
|
|
2410
|
+
reasons
|
|
2411
|
+
}
|
|
2412
|
+
extracted
|
|
2413
|
+
metrics {
|
|
2414
|
+
featureVersion
|
|
2415
|
+
candidateCount
|
|
2416
|
+
confidenceScore
|
|
2417
|
+
confidenceLabel
|
|
2418
|
+
stages {
|
|
2419
|
+
name
|
|
2420
|
+
status
|
|
2421
|
+
durationMs
|
|
2422
|
+
completedAt
|
|
2423
|
+
details
|
|
2424
|
+
}
|
|
2425
|
+
timings
|
|
2426
|
+
}
|
|
2427
|
+
webhook {
|
|
2428
|
+
configured
|
|
2429
|
+
url
|
|
2430
|
+
latestDeliveryId
|
|
2431
|
+
status
|
|
2432
|
+
attemptCount
|
|
2433
|
+
nextAttemptAt
|
|
2434
|
+
lastAttemptAt
|
|
2435
|
+
deliveredAt
|
|
2436
|
+
lastStatusCode
|
|
2437
|
+
lastError
|
|
2438
|
+
}
|
|
2439
|
+
error {
|
|
2440
|
+
code
|
|
2441
|
+
message
|
|
2442
|
+
}
|
|
2443
|
+
createdAt
|
|
2444
|
+
updatedAt
|
|
2445
|
+
startedAt
|
|
2446
|
+
completedAt
|
|
2447
|
+
FIELDS
|
|
2448
|
+
end
|
|
2449
|
+
|
|
2450
|
+
def scan_metrics_fields
|
|
2451
|
+
<<~FIELDS
|
|
2452
|
+
publisherSlug
|
|
2453
|
+
gameKey
|
|
2454
|
+
datasetKey
|
|
2455
|
+
since
|
|
2456
|
+
until
|
|
2457
|
+
generatedAt
|
|
2458
|
+
totalJobs
|
|
2459
|
+
completedJobs
|
|
2460
|
+
failedJobs
|
|
2461
|
+
failureRate
|
|
2462
|
+
averageShortlistSize
|
|
2463
|
+
confidenceDistribution
|
|
2464
|
+
feedback {
|
|
2465
|
+
total
|
|
2466
|
+
correct
|
|
2467
|
+
corrected
|
|
2468
|
+
unknown
|
|
2469
|
+
accuracy
|
|
2470
|
+
}
|
|
2471
|
+
featureVersions {
|
|
2472
|
+
featureVersion
|
|
2473
|
+
jobCount
|
|
2474
|
+
completedJobs
|
|
2475
|
+
failedJobs
|
|
2476
|
+
averageConfidence
|
|
2477
|
+
averageShortlistSize
|
|
2478
|
+
feedbackCount
|
|
2479
|
+
correctFeedback
|
|
2480
|
+
correctedFeedback
|
|
2481
|
+
feedbackAccuracy
|
|
2482
|
+
}
|
|
2483
|
+
FIELDS
|
|
2484
|
+
end
|
|
2485
|
+
|
|
2486
|
+
def scan_template_warning_fields
|
|
2487
|
+
<<~FIELDS
|
|
2488
|
+
code
|
|
2489
|
+
region
|
|
2490
|
+
message
|
|
2491
|
+
FIELDS
|
|
2492
|
+
end
|
|
2493
|
+
|
|
2494
|
+
def scan_template_region_fields
|
|
2495
|
+
<<~FIELDS
|
|
2496
|
+
id
|
|
2497
|
+
key
|
|
2498
|
+
label
|
|
2499
|
+
sortOrder
|
|
2500
|
+
shapeType
|
|
2501
|
+
geometry
|
|
2502
|
+
semanticType
|
|
2503
|
+
extractionMode
|
|
2504
|
+
lookupMode
|
|
2505
|
+
isRequired
|
|
2506
|
+
weight
|
|
2507
|
+
config
|
|
2508
|
+
FIELDS
|
|
2509
|
+
end
|
|
2510
|
+
|
|
2511
|
+
def scan_template_fields
|
|
2512
|
+
<<~FIELDS
|
|
2513
|
+
id
|
|
2514
|
+
key
|
|
2515
|
+
name
|
|
2516
|
+
status
|
|
2517
|
+
version
|
|
2518
|
+
isDefault
|
|
2519
|
+
publisherSlug
|
|
2520
|
+
gameKey
|
|
2521
|
+
datasetKey
|
|
2522
|
+
exampleFileId
|
|
2523
|
+
coordinateSpace
|
|
2524
|
+
cardAspectRatio
|
|
2525
|
+
notes
|
|
2526
|
+
config
|
|
2527
|
+
regions {
|
|
2528
|
+
#{scan_template_region_fields}
|
|
2529
|
+
}
|
|
2530
|
+
createdAt
|
|
2531
|
+
updatedAt
|
|
2532
|
+
FIELDS
|
|
2533
|
+
end
|
|
2534
|
+
|
|
2535
|
+
def scan_template_preview_region_fields
|
|
2536
|
+
<<~FIELDS
|
|
2537
|
+
key
|
|
2538
|
+
label
|
|
2539
|
+
semanticType
|
|
2540
|
+
extractionMode
|
|
2541
|
+
lookupMode
|
|
2542
|
+
rawText
|
|
2543
|
+
normalizedValue
|
|
2544
|
+
source
|
|
2545
|
+
status
|
|
2546
|
+
confidence
|
|
2547
|
+
FIELDS
|
|
2548
|
+
end
|
|
2549
|
+
|
|
2550
|
+
def game_scan_region_fields
|
|
2551
|
+
<<~FIELDS
|
|
2552
|
+
id
|
|
2553
|
+
key
|
|
2554
|
+
name
|
|
2555
|
+
description
|
|
2556
|
+
sortOrder
|
|
2557
|
+
isActive
|
|
2558
|
+
metadata
|
|
2559
|
+
createdAt
|
|
2560
|
+
updatedAt
|
|
2561
|
+
FIELDS
|
|
2562
|
+
end
|
|
2563
|
+
|
|
2255
2564
|
def import_job_connection_fields
|
|
2256
2565
|
connection_fields(import_job_fields)
|
|
2257
2566
|
end
|
|
@@ -29,6 +29,39 @@ module CardDB
|
|
|
29
29
|
File.new(data['fileUploadConfirm'], client: client)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
# Request a presigned URL, upload bytes directly to storage, and confirm the file.
|
|
33
|
+
def upload(
|
|
34
|
+
filename:,
|
|
35
|
+
content_type:,
|
|
36
|
+
body:,
|
|
37
|
+
size: nil,
|
|
38
|
+
purpose: nil,
|
|
39
|
+
is_public: false,
|
|
40
|
+
entity_type: nil,
|
|
41
|
+
entity_id: nil,
|
|
42
|
+
publisher_id: nil,
|
|
43
|
+
dataset_id: nil
|
|
44
|
+
)
|
|
45
|
+
upload_size = size || infer_upload_size(body)
|
|
46
|
+
raise ArgumentError, 'files.upload requires a positive size or an inferable body size' unless upload_size&.positive?
|
|
47
|
+
|
|
48
|
+
upload = request_upload(
|
|
49
|
+
input: {
|
|
50
|
+
filename: filename,
|
|
51
|
+
contentType: content_type,
|
|
52
|
+
size: upload_size,
|
|
53
|
+
isPublic: is_public,
|
|
54
|
+
entityType: entity_type || entity_type_for_purpose(purpose),
|
|
55
|
+
entityId: entity_id,
|
|
56
|
+
publisherId: publisher_id,
|
|
57
|
+
datasetId: dataset_id
|
|
58
|
+
}.compact
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
put_upload(upload.upload_url, content_type, body)
|
|
62
|
+
confirm_upload(id: upload.file.id)
|
|
63
|
+
end
|
|
64
|
+
|
|
32
65
|
# Delete a file. Requires a server-side secret credential.
|
|
33
66
|
# rubocop:disable Naming/PredicateMethod
|
|
34
67
|
def delete(id:)
|
|
@@ -38,6 +71,48 @@ module CardDB
|
|
|
38
71
|
!!data['fileDelete']
|
|
39
72
|
end
|
|
40
73
|
# rubocop:enable Naming/PredicateMethod
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def put_upload(upload_url, content_type, body)
|
|
78
|
+
response = Faraday.put(upload_url) do |request|
|
|
79
|
+
request.headers['Content-Type'] = content_type
|
|
80
|
+
request.body = body
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
return if response.success?
|
|
84
|
+
|
|
85
|
+
raise Error, "file upload failed with #{response.status}: #{response.body}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def infer_upload_size(body)
|
|
89
|
+
return body.bytesize if body.is_a?(String)
|
|
90
|
+
return body.bytesize if body.respond_to?(:bytesize)
|
|
91
|
+
return body.size if body.respond_to?(:size) && body.size.is_a?(Integer)
|
|
92
|
+
return body.stat.size if body.respond_to?(:stat)
|
|
93
|
+
|
|
94
|
+
infer_io_size(body)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def infer_io_size(body)
|
|
98
|
+
return nil unless body.respond_to?(:tell) && body.respond_to?(:seek)
|
|
99
|
+
|
|
100
|
+
original_position = body.tell
|
|
101
|
+
body.seek(0, IO::SEEK_END)
|
|
102
|
+
size = body.tell
|
|
103
|
+
body.seek(original_position, IO::SEEK_SET)
|
|
104
|
+
size
|
|
105
|
+
rescue IOError, SystemCallError
|
|
106
|
+
nil
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def entity_type_for_purpose(purpose)
|
|
110
|
+
return nil if purpose.nil? || purpose.to_s == 'scan_image'
|
|
111
|
+
return 'import' if purpose.to_s == 'import'
|
|
112
|
+
return 'scan_template_example' if purpose.to_s == 'scan_template_example'
|
|
113
|
+
|
|
114
|
+
purpose.to_s
|
|
115
|
+
end
|
|
41
116
|
end
|
|
42
117
|
end
|
|
43
118
|
end
|
|
@@ -74,6 +74,25 @@ module CardDB
|
|
|
74
74
|
Game.new(data['gameUpdate'], client: client)
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
# Get publisher-defined game region metadata for scan-capable clients.
|
|
78
|
+
def get_regions(publisher_slug: nil, game_key: nil, include_inactive: nil, cache: nil)
|
|
79
|
+
resolved_publisher = resolve_publisher(publisher_slug)
|
|
80
|
+
resolved_game = resolve_game(game_key)
|
|
81
|
+
validate_access!(resolved_publisher, resolved_game)
|
|
82
|
+
|
|
83
|
+
variables = build_variables(
|
|
84
|
+
publisherSlug: resolved_publisher,
|
|
85
|
+
gameKey: resolved_game,
|
|
86
|
+
includeInactive: include_inactive
|
|
87
|
+
)
|
|
88
|
+
key = cache_key('games', 'get_regions', **variables)
|
|
89
|
+
|
|
90
|
+
with_cache(key, resource: :games, cache: cache) do
|
|
91
|
+
data = connection.execute(QueryBuilder.game_scan_regions, variables)
|
|
92
|
+
(data['gameScanRegions'] || []).map { |region| GameScanRegion.new(region, client: client) }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
77
96
|
# Search for games
|
|
78
97
|
#
|
|
79
98
|
# @param publisher_slug [String, nil] Filter by publisher slug
|