dcc 0.2.0 → 0.2.2
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/.github/workflows/ci.yml +19 -0
- data/.github/workflows/release.yml +28 -0
- data/.gitignore +4 -0
- data/.rubocop_todo.yml +560 -10
- data/CHANGELOG.adoc +34 -1
- data/CONTRIBUTING.adoc +8 -1
- data/Gemfile +5 -7
- data/README.adoc +162 -7
- data/dcc.gemspec +9 -2
- data/docs/schema-versions.adoc +20 -7
- data/examples/build_minimal_dcc.rb +0 -0
- data/examples/convert_all_formats.rb +2 -1
- data/examples/parse_and_inspect.rb +0 -0
- data/examples/quantity_math_demo.rb +7 -7
- data/examples/validate_full.rb +1 -1
- data/lib/dcc/base/calibration_laboratory.rb +2 -1
- data/lib/dcc/base/condition.rb +5 -2
- data/lib/dcc/base/core_data.rb +8 -3
- data/lib/dcc/base/formula.rb +3 -3
- data/lib/dcc/base/identification.rb +6 -3
- data/lib/dcc/base/mathml.rb +33 -0
- data/lib/dcc/base/quantity.rb +2 -1
- data/lib/dcc/base/resp_person.rb +4 -2
- data/lib/dcc/base/text.rb +1 -1
- data/lib/dcc/base.rb +2 -1
- data/lib/dcc/builder/dsl.rb +1 -1
- data/lib/dcc/builder/session.rb +16 -14
- data/lib/dcc/builder.rb +3 -3
- data/lib/dcc/cli/cli.rb +152 -26
- data/lib/dcc/cli/formatters.rb +35 -7
- data/lib/dcc/cli.rb +1 -1
- data/lib/dcc/context_configuration.rb +2 -1
- data/lib/dcc/context_options.rb +4 -2
- data/lib/dcc/convert/csv.rb +27 -9
- data/lib/dcc/convert/html.rb +26 -110
- data/lib/dcc/convert/json.rb +10 -7
- data/lib/dcc/convert/liquid_filters.rb +58 -0
- data/lib/dcc/convert/result.rb +1 -1
- data/lib/dcc/convert/templates/report.liquid +250 -0
- data/lib/dcc/convert/yaml.rb +1 -1
- data/lib/dcc/convert.rb +1 -0
- data/lib/dcc/diff/change.rb +1 -1
- data/lib/dcc/diff/result.rb +8 -4
- data/lib/dcc/diff.rb +43 -11
- data/lib/dcc/error.rb +7 -0
- data/lib/dcc/extract/file.rb +3 -4
- data/lib/dcc/extract/formula/ast.rb +137 -0
- data/lib/dcc/extract/formula/evaluator.rb +357 -0
- data/lib/dcc/extract/formula/parser.rb +305 -0
- data/lib/dcc/extract/formula/quantity.rb +89 -0
- data/lib/dcc/extract/formula.rb +110 -0
- data/lib/dcc/extract/ring.rb +1 -1
- data/lib/dcc/extract.rb +1 -1
- data/lib/dcc/i18n/text_lookup.rb +8 -4
- data/lib/dcc/i18n.rb +1 -1
- data/lib/dcc/inspect/report.rb +51 -14
- data/lib/dcc/inspect.rb +1 -1
- data/lib/dcc/migrate/route.rb +44 -0
- data/lib/dcc/migrate/v2_to_v3.rb +445 -0
- data/lib/dcc/migrate.rb +130 -26
- data/lib/dcc/plugin/base.rb +64 -0
- data/lib/dcc/plugin/registry.rb +7 -2
- data/lib/dcc/plugin.rb +6 -2
- data/lib/dcc/quantity_format/formatter.rb +16 -10
- data/lib/dcc/quantity_format.rb +1 -1
- data/lib/dcc/quantity_math/complex.rb +252 -0
- data/lib/dcc/quantity_math/matrix.rb +217 -0
- data/lib/dcc/quantity_math/quantity.rb +1 -1
- data/lib/dcc/quantity_math/real.rb +6 -6
- data/lib/dcc/quantity_math.rb +31 -3
- data/lib/dcc/schema/version.rb +2 -2
- data/lib/dcc/server/app.html +282 -0
- data/lib/dcc/server/app.rb +171 -87
- data/lib/dcc/server/demo_cert.xml +244 -0
- data/lib/dcc/server/storage/entry.rb +1 -1
- data/lib/dcc/server/storage/memory.rb +3 -2
- data/lib/dcc/server/storage.rb +1 -1
- data/lib/dcc/server.rb +1 -1
- data/lib/dcc/si/base/complex.rb +1 -1
- data/lib/dcc/si/base/complex_list_xml_list.rb +1 -1
- data/lib/dcc/si/base/constant.rb +1 -1
- data/lib/dcc/si/base/coverage_interval.rb +36 -0
- data/lib/dcc/si/base/coverage_interval_mu.rb +1 -1
- data/lib/dcc/si/base/expanded_mu.rb +1 -1
- data/lib/dcc/si/base/expanded_unc.rb +34 -0
- data/lib/dcc/si/base/expanded_unc_xml_list.rb +3 -2
- data/lib/dcc/si/base/hybrid.rb +3 -2
- data/lib/dcc/si/base/measurement_uncertainty_univariate.rb +1 -1
- data/lib/dcc/si/base/real.rb +13 -3
- data/lib/dcc/si/base/real_list_xml_list.rb +5 -3
- data/lib/dcc/si/base/si_list.rb +3 -2
- data/lib/dcc/si/base/standard_mu.rb +1 -1
- data/lib/dcc/si/base.rb +5 -2
- data/lib/dcc/si/v1/coverage_interval.rb +8 -0
- data/lib/dcc/si/v1/expanded_unc.rb +1 -16
- data/lib/dcc/si/v1/measurement_uncertainty_univariate.rb +2 -1
- data/lib/dcc/si/v1.rb +5 -2
- data/lib/dcc/si/v2/coverage_interval.rb +8 -0
- data/lib/dcc/si/v2/coverage_interval_mu_xml_list.rb +4 -2
- data/lib/dcc/si/v2/expanded_mu_xml_list.rb +2 -1
- data/lib/dcc/si/v2/expanded_unc.rb +8 -0
- data/lib/dcc/si/v2/measurement_uncertainty_univariate.rb +2 -1
- data/lib/dcc/si/v2.rb +8 -3
- data/lib/dcc/signature/result.rb +2 -2
- data/lib/dcc/signature/signer.rb +31 -18
- data/lib/dcc/signature/verifier.rb +23 -21
- data/lib/dcc/signature.rb +1 -1
- data/lib/dcc/streaming/reader.rb +246 -0
- data/lib/dcc/streaming.rb +79 -0
- data/lib/dcc/transform/result.rb +1 -1
- data/lib/dcc/transform/xslt.rb +22 -12
- data/lib/dcc/transform.rb +5 -4
- data/lib/dcc/type/base64_binary.rb +5 -3
- data/lib/dcc/type/decimal_xml_list.rb +99 -13
- data/lib/dcc/type/iso_country_code.rb +3 -2
- data/lib/dcc/type/iso_language_code.rb +3 -2
- data/lib/dcc/type/schema_version.rb +3 -2
- data/lib/dcc/type/si_unit.rb +3 -2
- data/lib/dcc/type_guards.rb +3 -3
- data/lib/dcc/v2/calibration_laboratory.rb +2 -1
- data/lib/dcc/v2/condition.rb +22 -1
- data/lib/dcc/v2/configuration.rb +1 -1
- data/lib/dcc/v2/digital_calibration_certificate.rb +2 -1
- data/lib/dcc/v2/identification.rb +14 -1
- data/lib/dcc/v2/mathml.rb +8 -0
- data/lib/dcc/v2/measurement_meta_data_list.rb +2 -1
- data/lib/dcc/v2/quantity.rb +4 -2
- data/lib/dcc/v2.rb +4 -2
- data/lib/dcc/v3/calibration_laboratory.rb +2 -1
- data/lib/dcc/v3/configuration.rb +1 -1
- data/lib/dcc/v3/digital_calibration_certificate.rb +2 -1
- data/lib/dcc/v3/mathml.rb +8 -0
- data/lib/dcc/v3/measurement_meta_data_list.rb +2 -1
- data/lib/dcc/v3/quantity.rb +4 -2
- data/lib/dcc/v3.rb +12 -2
- data/lib/dcc/validate/business_rules.rb +23 -9
- data/lib/dcc/validate/issue.rb +2 -1
- data/lib/dcc/validate/result.rb +6 -4
- data/lib/dcc/validate/schematron/profile.rb +73 -18
- data/lib/dcc/validate/schematron/rule.rb +13 -3
- data/lib/dcc/validate/schematron/rules/administrative_data_completeness.rb +11 -4
- data/lib/dcc/validate/schematron/rules/base.rb +1 -2
- data/lib/dcc/validate/schematron/rules/date_range_check.rb +11 -5
- data/lib/dcc/validate/schematron/rules/id_refid_linking.rb +0 -3
- data/lib/dcc/validate/schematron/rules/influence_conditions_placement.rb +22 -12
- data/lib/dcc/validate/schematron/rules/iso_code_validation.rb +31 -14
- data/lib/dcc/validate/schematron/rules/language_code_dedup.rb +12 -5
- data/lib/dcc/validate/schematron/rules/non_si_declaration.rb +7 -4
- data/lib/dcc/validate/schematron/rules/release_format_check.rb +7 -4
- data/lib/dcc/validate/schematron/rules/schema_version_check.rb +13 -9
- data/lib/dcc/validate/schematron/rules/uncertainty_consistency.rb +39 -10
- data/lib/dcc/validate/schematron/rules/unit_format_check.rb +2 -3
- data/lib/dcc/validate/schematron/rules/used_methods_placement.rb +15 -10
- data/lib/dcc/validate/schematron/rules/used_software_placement.rb +15 -10
- data/lib/dcc/validate/schematron/rules/xml_list_spacing.rb +4 -3
- data/lib/dcc/validate/schematron/rules.rb +28 -14
- data/lib/dcc/validate/schematron.rb +1 -1
- data/lib/dcc/validate/xsd.rb +9 -3
- data/lib/dcc/validate.rb +1 -1
- data/lib/dcc/version.rb +1 -1
- data/lib/dcc/versioned_parser.rb +1 -1
- data/lib/dcc.rb +68 -10
- data/sample_dcc.xml +140 -0
- data/web/index.html +12 -0
- data/web/package-lock.json +1599 -0
- data/web/package.json +22 -0
- data/web/src/App.vue +349 -0
- data/web/src/lib/builder.ts +171 -0
- data/web/src/lib/dcc.ts +338 -0
- data/web/src/lib/units.json +5581 -0
- data/web/src/main.ts +5 -0
- data/web/src/style.css +27 -0
- data/web/tsconfig.json +15 -0
- data/web/vite.config.ts +14 -0
- metadata +148 -9
|
@@ -52,7 +52,7 @@ module Dcc
|
|
|
52
52
|
# @param exponent [Integer]
|
|
53
53
|
# @return [Real]
|
|
54
54
|
def **(exponent)
|
|
55
|
-
new_value = value
|
|
55
|
+
new_value = value**exponent
|
|
56
56
|
Real.new(
|
|
57
57
|
value: new_value,
|
|
58
58
|
unit: unit && "^#{exponent}",
|
|
@@ -75,7 +75,7 @@ module Dcc
|
|
|
75
75
|
return other.uncertainty if uncertainty.nil?
|
|
76
76
|
return uncertainty if other.uncertainty.nil?
|
|
77
77
|
|
|
78
|
-
sum_of_squares = (uncertainty
|
|
78
|
+
sum_of_squares = (uncertainty**2) + (other.uncertainty**2)
|
|
79
79
|
BigDecimal(Math.sqrt(sum_of_squares.to_f).to_s)
|
|
80
80
|
end
|
|
81
81
|
|
|
@@ -86,15 +86,15 @@ module Dcc
|
|
|
86
86
|
|
|
87
87
|
a_rel = uncertainty / value
|
|
88
88
|
b_rel = other.uncertainty / other.value
|
|
89
|
-
sum_of_squares = (a_rel
|
|
89
|
+
sum_of_squares = (a_rel**2) + (b_rel**2)
|
|
90
90
|
BigDecimal(Math.sqrt(sum_of_squares.to_f).to_s) * new_value.abs
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
# Power rule: σ_y = |n * x^(n-1)| * σ_x
|
|
94
|
-
def power_uncertainty(exponent,
|
|
94
|
+
def power_uncertainty(exponent, _new_value)
|
|
95
95
|
return nil unless uncertain?
|
|
96
96
|
|
|
97
|
-
derivative = exponent * (value
|
|
97
|
+
derivative = exponent * (value**(exponent - 1))
|
|
98
98
|
(derivative.abs * uncertainty)
|
|
99
99
|
end
|
|
100
100
|
|
|
@@ -113,4 +113,4 @@ module Dcc
|
|
|
113
113
|
end
|
|
114
114
|
end
|
|
115
115
|
end
|
|
116
|
-
end
|
|
116
|
+
end
|
data/lib/dcc/quantity_math.rb
CHANGED
|
@@ -5,10 +5,38 @@ require "bigdecimal/math"
|
|
|
5
5
|
|
|
6
6
|
module Dcc
|
|
7
7
|
# `Dcc::QuantityMath` provides BigDecimal-based arithmetic on D-SI
|
|
8
|
-
# quantities with proper uncertainty propagation.
|
|
9
|
-
#
|
|
8
|
+
# quantities with proper uncertainty propagation.
|
|
9
|
+
#
|
|
10
|
+
# `Real` is scalar: RSS (root-sum-square) for addition and subtraction,
|
|
11
|
+
# fractional for multiplication and division. `Matrix` is multivariate and
|
|
12
|
+
# carries a full covariance matrix, propagating by `C_y = J C J^T`.
|
|
13
|
+
# `Complex` is a Cartesian complex quantity built on a two-element
|
|
14
|
+
# `Matrix`, so the correlation between its real and imaginary parts
|
|
15
|
+
# survives every operation.
|
|
10
16
|
module QuantityMath
|
|
17
|
+
# Significant digits requested when taking the square root of a
|
|
18
|
+
# BigDecimal variance. Well past double precision; metrology
|
|
19
|
+
# uncertainties never carry this many digits. Ordinary arithmetic
|
|
20
|
+
# follows the caller's ambient BigDecimal context.
|
|
21
|
+
SQRT_PRECISION = 30
|
|
22
|
+
|
|
11
23
|
autoload :Quantity, "dcc/quantity_math/quantity"
|
|
12
24
|
autoload :Real, "dcc/quantity_math/real"
|
|
25
|
+
autoload :Matrix, "dcc/quantity_math/matrix"
|
|
26
|
+
autoload :Complex, "dcc/quantity_math/complex"
|
|
27
|
+
|
|
28
|
+
# Runs `block` with an unlimited BigDecimal precision, restoring the
|
|
29
|
+
# caller's `BigDecimal.limit` on the way out (including when the block
|
|
30
|
+
# raises). Everything feeding a covariance matrix goes through here: a
|
|
31
|
+
# rounded result is no longer exactly symmetric, and `Matrix`'s
|
|
32
|
+
# validation would reject our own output.
|
|
33
|
+
#
|
|
34
|
+
# @return [Object] whatever `block` returns
|
|
35
|
+
def self.exact_covariance
|
|
36
|
+
BigDecimal.save_limit do
|
|
37
|
+
BigDecimal.limit(0)
|
|
38
|
+
yield
|
|
39
|
+
end
|
|
40
|
+
end
|
|
13
41
|
end
|
|
14
|
-
end
|
|
42
|
+
end
|
data/lib/dcc/schema/version.rb
CHANGED
|
@@ -30,7 +30,7 @@ module Dcc
|
|
|
30
30
|
def normalize(v)
|
|
31
31
|
return nil if v.nil?
|
|
32
32
|
|
|
33
|
-
s = v.to_s.sub(/\Av/, "").gsub(
|
|
33
|
+
s = v.to_s.sub(/\Av/, "").gsub("_", ".")
|
|
34
34
|
s.empty? ? nil : s
|
|
35
35
|
end
|
|
36
36
|
|
|
@@ -56,7 +56,7 @@ module Dcc
|
|
|
56
56
|
# from the XML body.
|
|
57
57
|
def resolve_dcc(version, xml: nil)
|
|
58
58
|
v = normalize(version)
|
|
59
|
-
return detect_from_xml(xml) if v.nil? || v == "auto"
|
|
59
|
+
return detect_from_xml(xml) || DCC_LATEST if v.nil? || v == "auto"
|
|
60
60
|
return DCC_LATEST if v == "latest"
|
|
61
61
|
|
|
62
62
|
unless dcc?(v)
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
6
|
+
<title>DCC Validator</title>
|
|
7
|
+
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
|
8
|
+
<script src="https://unpkg.com/@tailwindcss/browser@4"></script>
|
|
9
|
+
<style>
|
|
10
|
+
[v-cloak] { display: none; }
|
|
11
|
+
@keyframes slideUp { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
|
|
12
|
+
.slide-up { animation: slideUp 0.4s ease-out both; }
|
|
13
|
+
.slide-up:nth-child(2) { animation-delay: .06s; }
|
|
14
|
+
.slide-up:nth-child(3) { animation-delay: .12s; }
|
|
15
|
+
.slide-up:nth-child(4) { animation-delay: .18s; }
|
|
16
|
+
.slide-up:nth-child(5) { animation-delay: .24s; }
|
|
17
|
+
.pulse-dot { animation: pd 2s ease-in-out infinite; }
|
|
18
|
+
@keyframes pd { 0%,100% { opacity:1; } 50% { opacity:.3; } }
|
|
19
|
+
</style>
|
|
20
|
+
</head>
|
|
21
|
+
<body class="bg-slate-950 text-slate-200 font-sans antialiased min-h-screen" style="font-family: 'Inter', system-ui, sans-serif;">
|
|
22
|
+
<div id="app" v-cloak>
|
|
23
|
+
|
|
24
|
+
<!-- Header bar -->
|
|
25
|
+
<div class="border-b border-slate-800 bg-slate-900/50 backdrop-blur sticky top-0 z-50">
|
|
26
|
+
<div class="max-w-5xl mx-auto px-6 py-3 flex items-center gap-4">
|
|
27
|
+
<div class="flex items-center gap-2">
|
|
28
|
+
<div class="w-8 h-8 rounded-lg bg-cyan-500/10 border border-cyan-500/30 flex items-center justify-center">
|
|
29
|
+
<span class="text-cyan-400 text-lg font-bold">D</span>
|
|
30
|
+
</div>
|
|
31
|
+
<span class="font-semibold text-slate-100">DCC Validator</span>
|
|
32
|
+
<span class="text-xs text-slate-500 hidden sm:inline">v{{ version }}</span>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="flex-1"></div>
|
|
35
|
+
<button @click="loadDemo" class="px-3 py-1.5 text-xs font-medium rounded-lg border border-slate-700 hover:border-cyan-500/50 hover:text-cyan-400 transition-colors">
|
|
36
|
+
Load Demo Certificate
|
|
37
|
+
</button>
|
|
38
|
+
<label class="cursor-pointer px-3 py-1.5 text-xs font-medium rounded-lg bg-cyan-500/10 border border-cyan-500/30 text-cyan-400 hover:bg-cyan-500/20 transition-colors">
|
|
39
|
+
Upload DCC XML
|
|
40
|
+
<input type="file" accept=".xml,.dcc" class="hidden" @change="onUpload($event)">
|
|
41
|
+
</label>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<!-- Drop zone (when no cert loaded) -->
|
|
46
|
+
<div v-if="!cert && !loading" class="max-w-2xl mx-auto px-6 pt-24">
|
|
47
|
+
<div
|
|
48
|
+
@dragover.prevent="dragActive = true"
|
|
49
|
+
@dragleave.prevent="dragActive = false"
|
|
50
|
+
@drop.prevent="onDrop($event)"
|
|
51
|
+
:class="['border-2 border-dashed rounded-2xl p-16 text-center transition-all', dragActive ? 'border-cyan-500 bg-cyan-500/5' : 'border-slate-800']"
|
|
52
|
+
>
|
|
53
|
+
<div class="text-5xl mb-4 opacity-30">📋</div>
|
|
54
|
+
<p class="text-slate-400 mb-1">Drag a DCC XML file here</p>
|
|
55
|
+
<p class="text-slate-600 text-sm">or click upload above</p>
|
|
56
|
+
<button @click="loadDemo" class="mt-6 px-4 py-2 text-sm font-medium rounded-lg bg-cyan-500/10 border border-cyan-500/30 text-cyan-400 hover:bg-cyan-500/20 transition-colors">
|
|
57
|
+
Try a demo certificate →
|
|
58
|
+
</button>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<!-- Loading -->
|
|
63
|
+
<div v-if="loading" class="max-w-2xl mx-auto px-6 pt-32 text-center">
|
|
64
|
+
<div class="inline-block w-8 h-8 border-2 border-cyan-500/30 border-t-cyan-500 rounded-full animate-spin mb-4"></div>
|
|
65
|
+
<p class="text-slate-400 text-sm">Validating certificate...</p>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<!-- Certificate view -->
|
|
69
|
+
<div v-if="cert && !loading" class="max-w-5xl mx-auto px-6 py-8 space-y-4">
|
|
70
|
+
|
|
71
|
+
<!-- Validity banner -->
|
|
72
|
+
<div :class="['rounded-2xl p-5 border slide-up flex items-center gap-4',
|
|
73
|
+
validation.ok ? 'bg-green-500/5 border-green-500/20' : 'bg-red-500/5 border-red-500/20']">
|
|
74
|
+
<div :class="['w-12 h-12 rounded-xl flex items-center justify-center text-2xl',
|
|
75
|
+
validation.ok ? 'bg-green-500/10' : 'bg-red-500/10']">
|
|
76
|
+
{{ validation.ok ? '✓' : '✕' }}
|
|
77
|
+
</div>
|
|
78
|
+
<div class="flex-1">
|
|
79
|
+
<div :class="['font-semibold text-lg', validation.ok ? 'text-green-400' : 'text-red-400']">
|
|
80
|
+
{{ validation.ok ? 'Certificate Valid' : 'Validation Failed' }}
|
|
81
|
+
</div>
|
|
82
|
+
<div class="text-sm text-slate-500">
|
|
83
|
+
{{ validation.errors.length }} error{{ validation.errors.length === 1 ? '' : 's' }},
|
|
84
|
+
{{ validation.warnings.length }} warning{{ validation.warnings.length === 1 ? '' : 's' }}
|
|
85
|
+
<span v-if="cert.schemaVersion"> · Schema v{{ cert.schemaVersion }}</span>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
<div class="flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-medium"
|
|
89
|
+
:class="validation.ok ? 'bg-green-500/10 text-green-400 border border-green-500/20' : 'bg-red-500/10 text-red-400 border border-red-500/20'">
|
|
90
|
+
<span class="w-1.5 h-1.5 rounded-full pulse-dot" :class="validation.ok ? 'bg-green-400' : 'bg-red-400'"></span>
|
|
91
|
+
{{ validation.ok ? 'VALID' : 'INVALID' }}
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<!-- Errors list -->
|
|
96
|
+
<div v-if="validation.errors.length" class="slide-up rounded-xl border border-red-500/15 bg-red-500/[0.03] overflow-hidden">
|
|
97
|
+
<div class="px-5 py-3 border-b border-red-500/10 text-xs font-semibold uppercase tracking-wide text-red-400/80">Validation Errors</div>
|
|
98
|
+
<div class="divide-y divide-red-500/5">
|
|
99
|
+
<div v-for="e in validation.errors" :key="e.line" class="px-5 py-3 flex gap-3">
|
|
100
|
+
<span class="text-red-500/50 text-xs font-mono pt-0.5">{{ e.line ? 'L'+e.line : '—' }}</span>
|
|
101
|
+
<span class="text-sm text-slate-300">{{ e.message }}</span>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<!-- Certificate identity -->
|
|
107
|
+
<div class="slide-up rounded-2xl border border-slate-800 bg-slate-900 overflow-hidden">
|
|
108
|
+
<div class="bg-gradient-to-br from-slate-900 to-slate-800/50 p-6 relative overflow-hidden">
|
|
109
|
+
<div class="absolute top-0 right-0 w-64 h-64 bg-cyan-500/5 rounded-full blur-3xl"></div>
|
|
110
|
+
<div class="relative flex justify-between items-start gap-4">
|
|
111
|
+
<div>
|
|
112
|
+
<div class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-[10px] font-semibold uppercase tracking-wider border border-cyan-500/30 text-cyan-400 bg-cyan-500/5 mb-3">
|
|
113
|
+
<span class="w-1 h-1 rounded-full bg-cyan-400 pulse-dot"></span>
|
|
114
|
+
Digital Calibration Certificate
|
|
115
|
+
</div>
|
|
116
|
+
<h1 class="text-2xl font-bold text-slate-100 mb-1">{{ cert.itemName || 'Calibration Certificate' }}</h1>
|
|
117
|
+
<p class="text-slate-400">{{ cert.labName || '' }}</p>
|
|
118
|
+
<p class="font-mono text-xs text-slate-600 mt-1.5">ID: {{ cert.uniqueId || '—' }}</p>
|
|
119
|
+
</div>
|
|
120
|
+
<div class="text-right border-l border-slate-700 pl-6">
|
|
121
|
+
<div class="text-[10px] uppercase tracking-wider text-slate-500">Schema</div>
|
|
122
|
+
<div class="font-mono text-green-400 font-medium">v{{ cert.schemaVersion }}</div>
|
|
123
|
+
<div class="mt-2 text-[10px] uppercase tracking-wider text-slate-500">Country</div>
|
|
124
|
+
<div class="font-mono text-lg">{{ cert.country || '—' }}</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<!-- Core data -->
|
|
131
|
+
<div v-if="cert.coreData" class="slide-up rounded-xl border border-slate-800 bg-slate-900 overflow-hidden">
|
|
132
|
+
<div class="px-5 py-3 border-b border-slate-800 flex items-center gap-2">
|
|
133
|
+
<span class="text-slate-500">⚙</span>
|
|
134
|
+
<span class="text-xs font-semibold uppercase tracking-wide text-slate-500">Core Data</span>
|
|
135
|
+
</div>
|
|
136
|
+
<div class="p-5 grid grid-cols-2 sm:grid-cols-3 gap-px bg-slate-800/50">
|
|
137
|
+
<div class="bg-slate-900 p-3" v-for="f in cert.coreData">
|
|
138
|
+
<div class="text-[10px] uppercase tracking-wider text-slate-600 mb-0.5">{{ f.label }}</div>
|
|
139
|
+
<div class="font-mono text-sm text-slate-200">{{ f.value }}</div>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<!-- Measurement results -->
|
|
145
|
+
<div v-if="cert.measurements && cert.measurements.length" class="slide-up rounded-xl border border-slate-800 bg-slate-900 overflow-hidden">
|
|
146
|
+
<div class="px-5 py-3 border-b border-slate-800 flex items-center gap-2">
|
|
147
|
+
<span class="text-slate-500">⚡</span>
|
|
148
|
+
<span class="text-xs font-semibold uppercase tracking-wide text-slate-500">Measurement Results</span>
|
|
149
|
+
</div>
|
|
150
|
+
<div class="p-5 space-y-3">
|
|
151
|
+
<div v-for="mr in cert.measurements" :key="mr.name" class="rounded-lg border border-slate-800 bg-slate-800/40 p-4">
|
|
152
|
+
<div class="font-medium text-slate-200 mb-3">{{ mr.name }}</div>
|
|
153
|
+
<div v-if="mr.results && mr.results.length" class="space-y-2">
|
|
154
|
+
<div v-for="r in mr.results" :key="r.name" class="flex items-center justify-between py-2 border-l-2 border-cyan-500/30 pl-3 bg-cyan-500/[0.03] rounded-r-lg">
|
|
155
|
+
<span class="text-sm text-slate-400">{{ r.name }}</span>
|
|
156
|
+
<span v-if="r.values" class="font-mono text-sm">
|
|
157
|
+
<span v-for="(v, i) in r.values" :key="i" class="ml-2">
|
|
158
|
+
<span class="text-green-400">{{ v.value }}</span>
|
|
159
|
+
<span class="text-slate-600"> {{ v.unit }}</span>
|
|
160
|
+
<span v-if="v.uncertainty" class="text-amber-400/60 text-xs"> ±{{ v.uncertainty }}</span>
|
|
161
|
+
</span>
|
|
162
|
+
</span>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<!-- Lab / Signer -->
|
|
170
|
+
<div class="slide-up grid sm:grid-cols-2 gap-4">
|
|
171
|
+
<div v-if="cert.lab" class="rounded-xl border border-slate-800 bg-slate-900 overflow-hidden">
|
|
172
|
+
<div class="px-5 py-3 border-b border-slate-800 text-xs font-semibold uppercase tracking-wide text-slate-500">Calibration Laboratory</div>
|
|
173
|
+
<div class="p-5">
|
|
174
|
+
<div class="font-medium text-slate-200 mb-1">{{ cert.lab.name }}</div>
|
|
175
|
+
<div v-if="cert.lab.email" class="font-mono text-xs text-slate-500">{{ cert.lab.email }}</div>
|
|
176
|
+
<div v-if="cert.lab.address" class="text-sm text-slate-400 mt-2">{{ cert.lab.address }}</div>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
<div v-if="cert.signers && cert.signers.length" class="rounded-xl border border-slate-800 bg-slate-900 overflow-hidden">
|
|
180
|
+
<div class="px-5 py-3 border-b border-slate-800 text-xs font-semibold uppercase tracking-wide text-slate-500">Responsible Persons</div>
|
|
181
|
+
<div class="p-5 space-y-2">
|
|
182
|
+
<div v-for="s in cert.signers" :key="s.name" class="flex items-center gap-3 rounded-lg border border-slate-800 bg-slate-800/40 px-3 py-2">
|
|
183
|
+
<div class="w-8 h-8 rounded-full bg-cyan-500 text-slate-900 flex items-center justify-center text-xs font-bold">{{ initials(s.name) }}</div>
|
|
184
|
+
<div class="flex-1">
|
|
185
|
+
<div class="text-sm font-medium text-slate-200">{{ s.name }}</div>
|
|
186
|
+
<div v-if="s.email" class="font-mono text-xs text-slate-500">{{ s.email }}</div>
|
|
187
|
+
</div>
|
|
188
|
+
<span v-if="s.main" class="px-2 py-0.5 rounded-full text-[10px] font-semibold uppercase tracking-wider text-green-400 border border-green-500/20 bg-green-500/10">Main</span>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<!-- XML source -->
|
|
195
|
+
<div class="slide-up rounded-xl border border-slate-800 bg-slate-900 overflow-hidden">
|
|
196
|
+
<button @click="showXml = !showXml" class="w-full px-5 py-3 flex items-center justify-between text-xs font-semibold uppercase tracking-wide text-slate-500 hover:bg-slate-800/50 transition-colors">
|
|
197
|
+
<span>XML Source</span>
|
|
198
|
+
<span>{{ showXml ? '▲' : '▼' }}</span>
|
|
199
|
+
</button>
|
|
200
|
+
<div v-if="showXml" class="p-5 overflow-auto max-h-96">
|
|
201
|
+
<pre class="text-xs text-slate-400 font-mono whitespace-pre-wrap">{{ rawXml }}</pre>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<script>
|
|
209
|
+
(function() {
|
|
210
|
+
const { createApp, ref, computed } = Vue;
|
|
211
|
+
|
|
212
|
+
createApp({
|
|
213
|
+
setup() {
|
|
214
|
+
const cert = ref(null);
|
|
215
|
+
const loading = ref(false);
|
|
216
|
+
const dragActive = ref(false);
|
|
217
|
+
const showXml = ref(false);
|
|
218
|
+
const rawXml = ref('');
|
|
219
|
+
const validation = ref({ ok: true, errors: [], warnings: [] });
|
|
220
|
+
const version = ref('0.2.0');
|
|
221
|
+
|
|
222
|
+
async function validate(xml) {
|
|
223
|
+
loading.value = true;
|
|
224
|
+
try {
|
|
225
|
+
const res = await fetch('/validate', {
|
|
226
|
+
method: 'POST',
|
|
227
|
+
headers: { 'Content-Type': 'application/xml' },
|
|
228
|
+
body: xml,
|
|
229
|
+
});
|
|
230
|
+
const data = await res.json();
|
|
231
|
+
cert.value = data.cert;
|
|
232
|
+
validation.value = data.validation;
|
|
233
|
+
rawXml.value = xml;
|
|
234
|
+
} catch (e) {
|
|
235
|
+
validation.value = { ok: false, errors: [{ line: null, message: 'Server error: ' + e.message }], warnings: [] };
|
|
236
|
+
}
|
|
237
|
+
loading.value = false;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function loadDemo() {
|
|
241
|
+
loading.value = true;
|
|
242
|
+
try {
|
|
243
|
+
const res = await fetch('/demo.xml');
|
|
244
|
+
const xml = await res.text();
|
|
245
|
+
await validate(xml);
|
|
246
|
+
} catch (e) {
|
|
247
|
+
loading.value = false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function onUpload(e) {
|
|
252
|
+
const file = e.target.files[0];
|
|
253
|
+
if (!file) return;
|
|
254
|
+
const reader = new FileReader();
|
|
255
|
+
reader.onload = (ev) => validate(ev.target.result);
|
|
256
|
+
reader.readAsText(file);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function onDrop(e) {
|
|
260
|
+
dragActive.value = false;
|
|
261
|
+
const file = e.dataTransfer.files[0];
|
|
262
|
+
if (!file) return;
|
|
263
|
+
const reader = new FileReader();
|
|
264
|
+
reader.onload = (ev) => validate(ev.target.result);
|
|
265
|
+
reader.readAsText(file);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function initials(name) {
|
|
269
|
+
if (!name) return '?';
|
|
270
|
+
const parts = name.trim().split(/\s+/);
|
|
271
|
+
if (parts.length === 1) return parts[0][0].toUpperCase();
|
|
272
|
+
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return { cert, loading, dragActive, showXml, rawXml, validation, version,
|
|
276
|
+
loadDemo, onUpload, onDrop, initials };
|
|
277
|
+
}
|
|
278
|
+
}).mount('#app');
|
|
279
|
+
})();
|
|
280
|
+
</script>
|
|
281
|
+
</body>
|
|
282
|
+
</html>
|