ox-tender-abstract 0.0.1 → 0.9.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83274157e46a8de668555603f4627528bbd27d091cf682157e24d5b47474fa98
4
- data.tar.gz: 6f4465d33a0b748e02803495259fa30d66c23c5cc9a2f8de0ec7ff56cb2787b7
3
+ metadata.gz: 2dfb0556afbbe98cebec1fbb413fbe4408d5258e6fbd79d090c99df94dc718d6
4
+ data.tar.gz: 7d3308d579e05d7c651848f2cd2d1459562ab85d9f3991c2ef4784637803df99
5
5
  SHA512:
6
- metadata.gz: 628bc14dadbd989cae0eef11bb608c05219e2432f59b9342b3dcfae22172db6e6dafcba6caf163477fb5e5792a659454243d02c413d2127dfafb0e11941b3923
7
- data.tar.gz: 70ecc6574d26ce57653540c420684718beaca51e485ab2c9c788a1575d07f1a8c6de0df2a2dcccf05287372c7b4d2f3cc3b8428a340e4f8b7c77f3a94a19823b
6
+ metadata.gz: aa20e0eeebbdebebc3cba7c36112db12a8bbc63bffffe9d6ce79f762520a9f28be0afc587fb47f22e9358facf118e6e80093fbe1b9a9387c356fecc43efcaacf
7
+ data.tar.gz: 3437061cb5eaa5c84de842c4d57056ef3fa5d080a0f972708db210c48081a807faa866aeb4b9296d759c4855b4c95c4b541f22ba4be924def0ce64a8afea1348
data/.rspec_status CHANGED
@@ -1,73 +1,76 @@
1
1
  example_id | status | run_time |
2
2
  ---------------------------------------------------------- | ------ | --------------- |
3
- ./spec/oxtenderabstract/archive_processor_spec.rb[1:1:1:1] | failed | 0.01328 seconds |
4
- ./spec/oxtenderabstract/archive_processor_spec.rb[1:1:2:1] | passed | 0.0008 seconds |
5
- ./spec/oxtenderabstract/archive_processor_spec.rb[1:1:3:1] | passed | 0.00013 seconds |
6
- ./spec/oxtenderabstract/archive_processor_spec.rb[1:1:4:1] | passed | 0.00037 seconds |
7
- ./spec/oxtenderabstract/archive_processor_spec.rb[1:2:1:1] | passed | 0.0008 seconds |
8
- ./spec/oxtenderabstract/archive_processor_spec.rb[1:2:2:1] | passed | 0.0088 seconds |
9
- ./spec/oxtenderabstract/archive_processor_spec.rb[1:3:1] | passed | 0.00154 seconds |
10
- ./spec/oxtenderabstract/archive_processor_spec.rb[1:3:2] | passed | 0.00028 seconds |
11
- ./spec/oxtenderabstract/archive_processor_spec.rb[1:4:1] | passed | 0.00084 seconds |
12
- ./spec/oxtenderabstract/archive_processor_spec.rb[1:4:2] | passed | 0.00088 seconds |
13
- ./spec/oxtenderabstract/archive_processor_spec.rb[1:4:3] | passed | 0.00072 seconds |
14
- ./spec/oxtenderabstract/archive_processor_spec.rb[1:5:1] | passed | 0.00034 seconds |
15
- ./spec/oxtenderabstract/archive_processor_spec.rb[1:6:1] | passed | 0.00004 seconds |
16
- ./spec/oxtenderabstract/client_spec.rb[1:1:1:1] | passed | 0.00032 seconds |
17
- ./spec/oxtenderabstract/client_spec.rb[1:1:2:1] | passed | 0.00046 seconds |
18
- ./spec/oxtenderabstract/client_spec.rb[1:1:3:1] | passed | 0.00004 seconds |
3
+ ./spec/oxtenderabstract/archive_processor_spec.rb[1:1:1:1] | failed | 0.01326 seconds |
4
+ ./spec/oxtenderabstract/archive_processor_spec.rb[1:1:2:1] | passed | 0.00126 seconds |
5
+ ./spec/oxtenderabstract/archive_processor_spec.rb[1:1:3:1] | passed | 0.0002 seconds |
6
+ ./spec/oxtenderabstract/archive_processor_spec.rb[1:1:4:1] | passed | 0.00108 seconds |
7
+ ./spec/oxtenderabstract/archive_processor_spec.rb[1:2:1:1] | passed | 0.00062 seconds |
8
+ ./spec/oxtenderabstract/archive_processor_spec.rb[1:2:2:1] | passed | 0.00915 seconds |
9
+ ./spec/oxtenderabstract/archive_processor_spec.rb[1:3:1] | passed | 0.00156 seconds |
10
+ ./spec/oxtenderabstract/archive_processor_spec.rb[1:3:2] | passed | 0.00014 seconds |
11
+ ./spec/oxtenderabstract/archive_processor_spec.rb[1:4:1] | passed | 0.00126 seconds |
12
+ ./spec/oxtenderabstract/archive_processor_spec.rb[1:4:2] | passed | 0.00119 seconds |
13
+ ./spec/oxtenderabstract/archive_processor_spec.rb[1:4:3] | passed | 0.00069 seconds |
14
+ ./spec/oxtenderabstract/archive_processor_spec.rb[1:5:1] | passed | 0.00004 seconds |
15
+ ./spec/oxtenderabstract/archive_processor_spec.rb[1:6:1] | passed | 0.00024 seconds |
16
+ ./spec/oxtenderabstract/client_spec.rb[1:1:1:1] | passed | 0.00084 seconds |
17
+ ./spec/oxtenderabstract/client_spec.rb[1:1:2:1] | passed | 0.00007 seconds |
18
+ ./spec/oxtenderabstract/client_spec.rb[1:1:3:1] | passed | 0.0004 seconds |
19
19
  ./spec/oxtenderabstract/client_spec.rb[1:1:4:1] | passed | 0.00004 seconds |
20
- ./spec/oxtenderabstract/client_spec.rb[1:2:1:1] | failed | 0.00484 seconds |
20
+ ./spec/oxtenderabstract/client_spec.rb[1:2:1:1] | failed | 0.00438 seconds |
21
21
  ./spec/oxtenderabstract/client_spec.rb[1:2:2:1] | passed | 0.00008 seconds |
22
- ./spec/oxtenderabstract/client_spec.rb[1:2:2:2] | passed | 0.00034 seconds |
23
- ./spec/oxtenderabstract/client_spec.rb[1:3:1:1] | failed | 0.00199 seconds |
24
- ./spec/oxtenderabstract/client_spec.rb[1:3:2:1] | passed | 0.00009 seconds |
25
- ./spec/oxtenderabstract/client_spec.rb[1:4:1] | passed | 0.00034 seconds |
26
- ./spec/oxtenderabstract/client_spec.rb[1:5:1] | passed | 0.0002 seconds |
27
- ./spec/oxtenderabstract/client_spec.rb[1:6:1:1] | passed | 0.00014 seconds |
22
+ ./spec/oxtenderabstract/client_spec.rb[1:2:2:2] | passed | 0.00029 seconds |
23
+ ./spec/oxtenderabstract/client_spec.rb[1:3:1:1] | failed | 0.00163 seconds |
24
+ ./spec/oxtenderabstract/client_spec.rb[1:3:2:1] | passed | 0.00005 seconds |
25
+ ./spec/oxtenderabstract/client_spec.rb[1:4:1] | passed | 0.00024 seconds |
26
+ ./spec/oxtenderabstract/client_spec.rb[1:5:1] | passed | 0.00017 seconds |
27
+ ./spec/oxtenderabstract/client_spec.rb[1:6:1:1] | passed | 0.00012 seconds |
28
28
  ./spec/oxtenderabstract/client_spec.rb[1:6:2:1] | passed | 0.00009 seconds |
29
- ./spec/oxtenderabstract/client_spec.rb[1:6:3:1] | passed | 0.00016 seconds |
30
- ./spec/oxtenderabstract/client_spec.rb[1:6:3:2] | passed | 0.00015 seconds |
31
- ./spec/oxtenderabstract/client_spec.rb[1:7:1] | passed | 0.00011 seconds |
32
- ./spec/oxtenderabstract/configuration_spec.rb[1:1:1] | passed | 0.00033 seconds |
29
+ ./spec/oxtenderabstract/client_spec.rb[1:6:3:1] | passed | 0.00031 seconds |
30
+ ./spec/oxtenderabstract/client_spec.rb[1:6:3:2] | passed | 0.00016 seconds |
31
+ ./spec/oxtenderabstract/client_spec.rb[1:7:1] | passed | 0.00017 seconds |
32
+ ./spec/oxtenderabstract/configuration_spec.rb[1:1:1] | passed | 0.00029 seconds |
33
33
  ./spec/oxtenderabstract/configuration_spec.rb[1:2:1] | passed | 0.00003 seconds |
34
34
  ./spec/oxtenderabstract/configuration_spec.rb[1:2:2] | passed | 0.00003 seconds |
35
35
  ./spec/oxtenderabstract/configuration_spec.rb[1:2:3] | passed | 0.00002 seconds |
36
- ./spec/oxtenderabstract/configuration_spec.rb[1:3:1] | passed | 0.00084 seconds |
37
- ./spec/oxtenderabstract/configuration_spec.rb[1:3:2] | passed | 0.00017 seconds |
38
- ./spec/oxtenderabstract/configuration_spec.rb[1:3:3] | passed | 0.0002 seconds |
39
- ./spec/oxtenderabstract/configuration_spec.rb[1:3:4] | passed | 0.00033 seconds |
40
- ./spec/oxtenderabstract/configuration_spec.rb[1:4:1] | passed | 0.00003 seconds |
41
- ./spec/oxtenderabstract/configuration_spec.rb[1:4:2] | passed | 0.00002 seconds |
42
- ./spec/oxtenderabstract/configuration_spec.rb[1:4:3] | passed | 0.00002 seconds |
43
- ./spec/oxtenderabstract/configuration_spec.rb[1:4:4] | passed | 0.00002 seconds |
44
- ./spec/oxtenderabstract/configuration_spec.rb[1:4:5] | passed | 0.00002 seconds |
45
- ./spec/oxtenderabstract/configuration_spec.rb[1:4:6] | passed | 0.00003 seconds |
46
- ./spec/oxtenderabstract/result_spec.rb[1:1:1] | passed | 0.00005 seconds |
36
+ ./spec/oxtenderabstract/configuration_spec.rb[1:3:1] | passed | 0.00031 seconds |
37
+ ./spec/oxtenderabstract/configuration_spec.rb[1:3:2] | passed | 0.00074 seconds |
38
+ ./spec/oxtenderabstract/configuration_spec.rb[1:3:3] | passed | 0.00047 seconds |
39
+ ./spec/oxtenderabstract/configuration_spec.rb[1:3:4] | passed | 0.00027 seconds |
40
+ ./spec/oxtenderabstract/configuration_spec.rb[1:4:1] | passed | 0.00004 seconds |
41
+ ./spec/oxtenderabstract/configuration_spec.rb[1:4:2] | passed | 0.00004 seconds |
42
+ ./spec/oxtenderabstract/configuration_spec.rb[1:4:3] | passed | 0.00003 seconds |
43
+ ./spec/oxtenderabstract/configuration_spec.rb[1:4:4] | passed | 0.00003 seconds |
44
+ ./spec/oxtenderabstract/configuration_spec.rb[1:4:5] | passed | 0.00003 seconds |
45
+ ./spec/oxtenderabstract/configuration_spec.rb[1:4:6] | passed | 0.00005 seconds |
46
+ ./spec/oxtenderabstract/result_spec.rb[1:1:1] | passed | 0.00007 seconds |
47
47
  ./spec/oxtenderabstract/result_spec.rb[1:2:1] | passed | 0.00004 seconds |
48
- ./spec/oxtenderabstract/result_spec.rb[1:3:1] | passed | 0.00002 seconds |
48
+ ./spec/oxtenderabstract/result_spec.rb[1:3:1] | passed | 0.00003 seconds |
49
49
  ./spec/oxtenderabstract/result_spec.rb[1:3:2] | passed | 0.00002 seconds |
50
50
  ./spec/oxtenderabstract/result_spec.rb[1:4:1] | passed | 0.00002 seconds |
51
51
  ./spec/oxtenderabstract/result_spec.rb[1:4:2] | passed | 0.00002 seconds |
52
- ./spec/oxtenderabstract/xml_parser_spec.rb[1:1:1:1] | passed | 0.00009 seconds |
52
+ ./spec/oxtenderabstract/xml_parser_spec.rb[1:1:1:1] | passed | 0.00012 seconds |
53
53
  ./spec/oxtenderabstract/xml_parser_spec.rb[1:1:2:1] | passed | 0.00004 seconds |
54
- ./spec/oxtenderabstract/xml_parser_spec.rb[1:1:3:1] | failed | 0.00075 seconds |
55
- ./spec/oxtenderabstract/xml_parser_spec.rb[1:1:3:2] | passed | 0.00058 seconds |
56
- ./spec/oxtenderabstract/xml_parser_spec.rb[1:1:3:3] | failed | 0.00052 seconds |
57
- ./spec/oxtenderabstract/xml_parser_spec.rb[1:1:4:1] | passed | 0.00007 seconds |
58
- ./spec/oxtenderabstract/xml_parser_spec.rb[1:1:5:1] | passed | 0.00007 seconds |
59
- ./spec/oxtenderabstract/xml_parser_spec.rb[1:1:6:1] | passed | 0.00007 seconds |
60
- ./spec/oxtenderabstract/xml_parser_spec.rb[1:2:1:1] | failed | 0.00009 seconds |
61
- ./spec/oxtenderabstract/xml_parser_spec.rb[1:2:2:1] | failed | 0.00009 seconds |
62
- ./spec/oxtenderabstract/xml_parser_spec.rb[1:2:3:1] | failed | 0.00014 seconds |
54
+ ./spec/oxtenderabstract/xml_parser_spec.rb[1:1:3:1] | failed | 0.00122 seconds |
55
+ ./spec/oxtenderabstract/xml_parser_spec.rb[1:1:3:2] | passed | 0.00162 seconds |
56
+ ./spec/oxtenderabstract/xml_parser_spec.rb[1:1:3:3] | failed | 0.00097 seconds |
57
+ ./spec/oxtenderabstract/xml_parser_spec.rb[1:1:4:1] | passed | 0.00336 seconds |
58
+ ./spec/oxtenderabstract/xml_parser_spec.rb[1:1:4:2] | passed | 0.00176 seconds |
59
+ ./spec/oxtenderabstract/xml_parser_spec.rb[1:1:4:3] | passed | 0.0009 seconds |
60
+ ./spec/oxtenderabstract/xml_parser_spec.rb[1:1:5:1] | passed | 0.00012 seconds |
61
+ ./spec/oxtenderabstract/xml_parser_spec.rb[1:1:6:1] | passed | 0.00008 seconds |
62
+ ./spec/oxtenderabstract/xml_parser_spec.rb[1:1:7:1] | passed | 0.00008 seconds |
63
+ ./spec/oxtenderabstract/xml_parser_spec.rb[1:2:1:1] | failed | 0.0001 seconds |
64
+ ./spec/oxtenderabstract/xml_parser_spec.rb[1:2:2:1] | failed | 0.00007 seconds |
65
+ ./spec/oxtenderabstract/xml_parser_spec.rb[1:2:3:1] | failed | 0.00012 seconds |
63
66
  ./spec/oxtenderabstract/xml_parser_spec.rb[1:3:1] | passed | 0.00005 seconds |
64
- ./spec/oxtenderabstract/xml_parser_spec.rb[1:3:2] | passed | 0.00004 seconds |
67
+ ./spec/oxtenderabstract/xml_parser_spec.rb[1:3:2] | passed | 0.00005 seconds |
65
68
  ./spec/oxtenderabstract/xml_parser_spec.rb[1:3:3] | passed | 0.00004 seconds |
66
- ./spec/oxtenderabstract/xml_parser_spec.rb[1:3:4] | passed | 0.00006 seconds |
67
- ./spec/oxtenderabstract/xml_parser_spec.rb[1:4:1:1] | failed | 0.00029 seconds |
68
- ./spec/oxtenderabstract/xml_parser_spec.rb[1:4:2:1] | failed | 0.00006 seconds |
69
- ./spec/oxtenderabstract_spec.rb[1:1:1] | passed | 0.00031 seconds |
69
+ ./spec/oxtenderabstract/xml_parser_spec.rb[1:3:4] | passed | 0.00005 seconds |
70
+ ./spec/oxtenderabstract/xml_parser_spec.rb[1:4:1:1] | failed | 0.00045 seconds |
71
+ ./spec/oxtenderabstract/xml_parser_spec.rb[1:4:2:1] | failed | 0.00007 seconds |
72
+ ./spec/oxtenderabstract_spec.rb[1:1:1] | passed | 0.00053 seconds |
70
73
  ./spec/oxtenderabstract_spec.rb[1:2:1] | passed | 0.00003 seconds |
71
74
  ./spec/oxtenderabstract_spec.rb[1:3:1] | passed | 0.00003 seconds |
72
- ./spec/oxtenderabstract_spec.rb[1:4:1:1] | passed | 0.00018 seconds |
73
- ./spec/oxtenderabstract_spec.rb[1:4:2:1] | passed | 0.00013 seconds |
75
+ ./spec/oxtenderabstract_spec.rb[1:4:1:1] | passed | 0.00024 seconds |
76
+ ./spec/oxtenderabstract_spec.rb[1:4:2:1] | passed | 0.00017 seconds |
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
- ## [0.1.0] - 2025-07-10
1
+ ## [0.9.0] - 2025-07-15
2
2
 
3
3
  - Initial release
4
+ - Added support for parsing tender documents
5
+ - Added support for parsing contract documents
6
+ - Added support for parsing organization documents
7
+ - Added support for parsing generic documents
8
+ - Added support for parsing attachments
9
+ - Added support for parsing tender documents
10
+ - Added support for parsing contract documents
11
+ - Added support for parsing organization documents
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OxTenderAbstract
4
- VERSION = '0.0.1'
4
+ VERSION = '0.9.1'
5
5
  end
@@ -330,6 +330,7 @@ module OxTenderAbstract
330
330
  tender_data[:procedure_info] = extract_procedure_info(doc, namespaces)
331
331
  tender_data[:lot_info] = extract_lot_information(doc, namespaces)
332
332
  tender_data[:guarantee_info] = extract_guarantee_info(doc, namespaces)
333
+ tender_data[:purchase_objects] = extract_purchase_objects_info(doc, namespaces)
333
334
 
334
335
  # Clean up empty values
335
336
  tender_data.compact
@@ -407,11 +408,14 @@ module OxTenderAbstract
407
408
  def extract_price_from_text(text)
408
409
  return nil if text.nil? || text.empty?
409
410
 
410
- # Remove any non-digit characters except decimal separator
411
- cleaned = text.gsub(/[^\d.,]/, '')
411
+ # Remove any non-digit characters except decimal separator and spaces
412
+ cleaned = text.gsub(/[^\d\s.,]/, '').strip
412
413
  return nil if cleaned.empty?
413
414
 
414
- # Convert to string with proper decimal separator
415
+ # Remove spaces (used as thousand separators in Russian format)
416
+ cleaned = cleaned.tr(' ', '')
417
+
418
+ # Convert comma to dot for decimal separator
415
419
  result = cleaned.tr(',', '.')
416
420
  return result if result =~ /^\d+(\.\d+)?$/
417
421
 
@@ -474,6 +478,171 @@ module OxTenderAbstract
474
478
  { lots: lots, lots_count: lots.size }
475
479
  end
476
480
 
481
+ def extract_purchase_objects_info(doc, namespaces)
482
+ purchase_objects = []
483
+ total_sum = nil
484
+
485
+ begin
486
+ # Find purchase objects nodes - use more defensive approach
487
+ purchase_object_nodes = doc.xpath(
488
+ '//ns5:purchaseObjectsInfo//ns4:purchaseObject | //purchaseObjectsInfo//purchaseObject', namespaces
489
+ )
490
+
491
+ purchase_objects = purchase_object_nodes.map do |object_node|
492
+ extract_purchase_object_data(object_node, namespaces)
493
+ end.compact
494
+
495
+ # Extract total sum from purchaseObjectsInfo
496
+ total_sum = extract_price_from_text(find_text_with_namespaces(doc, [
497
+ '//ns5:purchaseObjectsInfo//ns4:totalSum',
498
+ '//purchaseObjectsInfo//totalSum',
499
+ '//ns5:notDrugPurchaseObjectsInfo/ns4:totalSum',
500
+ '//notDrugPurchaseObjectsInfo/totalSum'
501
+ ], namespaces))
502
+
503
+ # Extract quantity undefined flag
504
+ quantity_undefined = find_text_with_namespaces(doc, [
505
+ '//ns5:purchaseObjectsInfo//ns5:quantityUndefined',
506
+ '//purchaseObjectsInfo//quantityUndefined'
507
+ ], namespaces) == 'true'
508
+
509
+ return {} if purchase_objects.empty? && total_sum.nil?
510
+
511
+ {
512
+ objects: purchase_objects,
513
+ objects_count: purchase_objects.size,
514
+ total_sum: total_sum,
515
+ quantity_undefined: quantity_undefined
516
+ }.compact
517
+ rescue StandardError => e
518
+ log_debug "Error extracting purchase objects: #{e.message}"
519
+ {}
520
+ end
521
+ end
522
+
523
+ def extract_purchase_object_data(object_node, namespaces)
524
+ # Basic object information
525
+ object_data = {
526
+ sid: extract_text_from_node(object_node, './/ns4:sid | .//sid'),
527
+ external_sid: extract_text_from_node(object_node, './/ns4:externalSid | .//externalSid'),
528
+ name: extract_text_from_node(object_node, './/ns4:name | .//name'),
529
+ price: extract_price_from_text(extract_text_from_node(object_node, './/ns4:price | .//price')),
530
+ quantity: extract_text_from_node(object_node, './/ns4:quantity/ns4:value | .//quantity/value')&.to_i,
531
+ sum: extract_price_from_text(extract_text_from_node(object_node, './/ns4:sum | .//sum')),
532
+ type: extract_text_from_node(object_node, './/ns4:type | .//type'),
533
+ hierarchy_type: extract_text_from_node(object_node, './/ns4:hierarchyType | .//hierarchyType'),
534
+ volume_specifying_method: extract_text_from_node(object_node,
535
+ './/ns4:volumeSpecifyingMethod | .//volumeSpecifyingMethod')
536
+ }
537
+
538
+ # KTRU information
539
+ ktru_node = object_node.at_xpath('.//ns4:KTRU | .//KTRU', namespaces)
540
+ if ktru_node
541
+ object_data[:ktru] = {
542
+ code: extract_text_from_node(ktru_node, './/ns2:code | .//code'),
543
+ name: extract_text_from_node(ktru_node, './/ns2:name | .//name'),
544
+ version_id: extract_text_from_node(ktru_node, './/ns2:versionId | .//versionId'),
545
+ version_number: extract_text_from_node(ktru_node, './/ns2:versionNumber | .//versionNumber')
546
+ }
547
+ end
548
+
549
+ # OKPD2 information
550
+ okpd2_node = object_node.at_xpath('.//ns4:OKPD2 | .//OKPD2', namespaces)
551
+ if okpd2_node
552
+ object_data[:okpd2] = {
553
+ code: extract_text_from_node(okpd2_node, './/ns2:OKPDCode | .//OKPDCode'),
554
+ name: extract_text_from_node(okpd2_node, './/ns2:OKPDName | .//OKPDName')
555
+ }
556
+ end
557
+
558
+ # OKEI information (units of measurement)
559
+ okei_node = object_node.at_xpath('.//ns4:OKEI | .//OKEI', namespaces)
560
+ if okei_node
561
+ object_data[:okei] = {
562
+ code: extract_text_from_node(okei_node, './/ns2:code | .//code'),
563
+ national_code: extract_text_from_node(okei_node, './/ns2:nationalCode | .//nationalCode'),
564
+ name: extract_text_from_node(okei_node, './/ns2:name | .//name')
565
+ }
566
+ end
567
+
568
+ # Restrictions info
569
+ restrictions_node = object_node.at_xpath('.//ns4:restrictionsInfo | .//restrictionsInfo', namespaces)
570
+ if restrictions_node
571
+ object_data[:restrictions] = {
572
+ is_preference_rf: extract_text_from_node(restrictions_node, './/ns4:isPreferenseRFPurchaseObjects | .//isPreferenseRFPurchaseObjects') == 'true'
573
+ }
574
+ end
575
+
576
+ # Extract characteristics (simplified - just count and basic info)
577
+ characteristics_nodes = object_node.xpath(
578
+ './/ns4:characteristics//ns4:characteristicsUsingReferenceInfo | .//characteristics//characteristicsUsingReferenceInfo', namespaces
579
+ )
580
+ characteristics_nodes += object_node.xpath(
581
+ './/ns4:characteristics//ns4:characteristicsUsingTextForm | .//characteristics//characteristicsUsingTextForm', namespaces
582
+ )
583
+
584
+ if characteristics_nodes.any?
585
+ object_data[:characteristics] = {
586
+ count: characteristics_nodes.size,
587
+ details: characteristics_nodes.first(5).map do |char_node|
588
+ {
589
+ name: extract_text_from_node(char_node, './/ns4:name | .//name'),
590
+ type: extract_text_from_node(char_node, './/ns4:type | .//type')
591
+ }
592
+ end
593
+ }
594
+ end
595
+
596
+ # Determine the actual product name from available sources
597
+ # Priority: KTRU name > OKPD2 name > name field
598
+ product_name = nil
599
+ product_name = if object_data[:ktru] && object_data[:ktru][:name] && !object_data[:ktru][:name].empty?
600
+ object_data[:ktru][:name]
601
+ elsif object_data[:okpd2] && object_data[:okpd2][:name] && !object_data[:okpd2][:name].empty?
602
+ object_data[:okpd2][:name]
603
+ else
604
+ object_data[:name]
605
+ end
606
+
607
+ object_data[:product_name] = product_name
608
+
609
+ # Add field description indicating what the 'name' field actually contains
610
+ object_data[:name_type] = determine_name_type(object_data[:name])
611
+
612
+ object_data.compact
613
+ end
614
+
615
+ private
616
+
617
+ def determine_name_type(name)
618
+ return nil unless name
619
+
620
+ # Common patterns that indicate this is a characteristic, not a product name
621
+ characteristic_patterns = [
622
+ /соответствие\s+требованиям/i,
623
+ /минимальный\s+.*срок/i,
624
+ /количество\s+/i,
625
+ /размер\s+/i,
626
+ /объем\s+/i,
627
+ /вес\s+/i,
628
+ /цвет\s+/i,
629
+ /материал\s+/i,
630
+ /тип\s+/i,
631
+ /класс\s+/i,
632
+ /группа\s+/i,
633
+ /категория\s+/i,
634
+ /способ\s+/i,
635
+ /метод\s+/i,
636
+ /технология\s+/i
637
+ ]
638
+
639
+ if characteristic_patterns.any? { |pattern| name.match?(pattern) }
640
+ 'characteristic'
641
+ else
642
+ 'product_name'
643
+ end
644
+ end
645
+
477
646
  def extract_guarantee_info(doc, namespaces)
478
647
  {
479
648
  contract_guarantee_part: find_text_with_namespaces(doc, [
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ox-tender-abstract
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - smolev