mediserv 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5204dd01be4fb9c2d2094f8d905239744bfbc3994a37af6a03ba9498ae26749c
4
+ data.tar.gz: ec11e152aef07451253f7833697957d1a314e5668c0a5c00de8bc5432781dd84
5
+ SHA512:
6
+ metadata.gz: a043815bb4d3162fb02476447f4f5651d3f6a567f84051a772932db0f32f181b19e8508c56af1c34611e96eac09c00296bb35d05945553cca149aae67753fd14
7
+ data.tar.gz: 0cdde28234817de97f0dd3425feba8c868d07766f4e7faa68c7e1c5b1b8ed57fc994bd1c605f2dbb110ed04982645b2a36fc0445f943459dd96f408f8996f196
@@ -0,0 +1,33 @@
1
+ module MediServ::API
2
+ require 'toml-rb'
3
+
4
+ require_relative "encoder"
5
+ require_relative "error"
6
+ require_relative "types"
7
+
8
+ class Client
9
+ # * *Args* :
10
+ def initialize(clientId, storage)
11
+ @storage = storage
12
+ @encoder = MediServ::API::Encoder.new
13
+ @default_header = {
14
+ programm: 'MediServ gem',
15
+ programm_version: '0.0.1', # TODO: Load from gemspec
16
+ version: 'v2',
17
+ kundennummer: clientId,
18
+ stapelnummer: 0,
19
+ }
20
+ end
21
+
22
+ def batch(id, invoices, created_at: DateTime.now)
23
+ b = MediServ::API::Batch.new({
24
+ allgemein: @default_header.merge({
25
+ stapelnummer: id,
26
+ }),
27
+ rechnungen: invoices,
28
+ created_at: created_at,
29
+ })
30
+ @storage.put(id, @encoder.encode(b.to_h))
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,66 @@
1
+ module MediServ::API
2
+ require_relative "format"
3
+
4
+ class Encoder
5
+ def encode(h)
6
+ build_tree(Node.new, h).to_ini
7
+ end
8
+
9
+ def build_tree(node, h)
10
+ h.each do |k, v|
11
+ case v
12
+ when Hash
13
+ child = Node.new(name: [node.name, k].compact.join('.'))
14
+ node.children << child
15
+ build_tree(child, v)
16
+ else
17
+ node.attributes[k] = v
18
+ end
19
+ end
20
+ node
21
+ end
22
+ end
23
+
24
+ class Node
25
+ include Format
26
+
27
+ attr_accessor :name, :attributes, :children
28
+
29
+ def initialize(name: nil, attributes: {}, children: [])
30
+ @name = name
31
+ @attributes = attributes
32
+ @children = children
33
+ end
34
+
35
+ def to_ini(io = StringIO.new)
36
+ if name
37
+ io << '['
38
+ io << name
39
+ io << ']'
40
+ io << "\n"
41
+ end
42
+ attributes.each do |k, v|
43
+ next unless k && v
44
+ io << k.to_s
45
+ io << '='
46
+
47
+ v = case v
48
+ when Float
49
+ io << fmt_amount(v)
50
+ when String
51
+ io << '"'
52
+ io << v.tr("\n", '')
53
+ io << '"'
54
+ else
55
+ io << v
56
+ end
57
+ io << "\n"
58
+ end
59
+ children.each do |c|
60
+ io << "\n"
61
+ c.to_ini(io)
62
+ end
63
+ io.string.encode('windows-1252')
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,13 @@
1
+ module MediServ::API
2
+ class MissingError < StandardError
3
+ end
4
+ class BadRequestError < StandardError
5
+ end
6
+ class PermissionError < StandardError
7
+ end
8
+ class UnauthenticatedError < StandardError
9
+ end
10
+ class ConflictError < StandardError
11
+ # TODO: Add resource
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ module MediServ::API
2
+ module Format
3
+ def fmt_date(d)
4
+ d ? d.strftime('%d.%m.%Y') : ''
5
+ end
6
+
7
+ def fmt_time(t)
8
+ t ? t.strftime('%H:%M') : ''
9
+ end
10
+
11
+ def fmt_amount(n)
12
+ n ? sprintf('%0.2f', n) : '0.00'
13
+ end
14
+
15
+ def fmt_string(s, max: nil)
16
+ max ? s[0..max] : s
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,7 @@
1
+ module MediServ::API
2
+ class Storage
3
+ def put(k, v)
4
+ raise StandardError, 'unimplemented method #put'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,378 @@
1
+ module MediServ::API
2
+ require 'dry-types'
3
+ require 'dry-struct'
4
+
5
+ require_relative "format"
6
+
7
+ module Types
8
+ include Dry.Types()
9
+ end
10
+
11
+ class Struct < Dry::Struct
12
+ include Format
13
+ end
14
+
15
+ class Recipient < Struct
16
+ # Nummer des Empfängers
17
+ attribute :nummer, Types::Strict::Integer
18
+ # Art des Empfängers (Garant oder Gesetz_Vertreter)
19
+ attribute :art, Types::Strict::String
20
+ # Anrede in der Sprache der Rechnung
21
+ attribute :anrede, Types::Strict::String
22
+ # Titel in der Sprache der Rechnung
23
+ attribute :titel, Types::Strict::String
24
+ # Name
25
+ attribute :name, Types::Strict::String
26
+ # Vorname
27
+ attribute :vorname, Types::Strict::String
28
+ # Strasse mit Nummer
29
+ attribute :strasse, Types::Strict::String.optional.default(nil)
30
+ # Land (CH / D / F / ...)
31
+ attribute :land, Types::Strict::String
32
+ # Postleitzahl
33
+ attribute :plz, Types::Strict::String
34
+ # Ort
35
+ attribute :ort, Types::Strict::String
36
+ # Geburtsdatum
37
+ attribute :geburtsdatum, Types::Strict::Date
38
+ end
39
+
40
+ class Patient < Struct
41
+ # Patientennummer
42
+ attribute :nummer, Types::Strict::Integer
43
+ # Anrede in der Sprache der Rechnung
44
+ attribute :anrede, Types::Strict::String
45
+ # Titel in der Sprache der Rechnung
46
+ attribute :titel, Types::Strict::String
47
+ # Name
48
+ attribute :name, Types::Strict::String
49
+ # Vorname
50
+ attribute :vorname, Types::Strict::String
51
+ # Strasse mit Nummer
52
+ attribute :strasse, Types::Strict::String.optional.default(nil)
53
+ # Zusatzadresse
54
+ attribute :zusatz, Types::Strict::String.optional.default(nil)
55
+ # Land (CH / D / F / ...)
56
+ attribute :land, Types::Strict::String
57
+ # Postleitzahl
58
+ attribute :plz, Types::Strict::String
59
+ # Ort
60
+ attribute :ort, Types::Strict::String
61
+ # Geburtsdatum
62
+ attribute :geburtsdatum, Types::Strict::Date
63
+ end
64
+
65
+ class InvoiceItem < Struct
66
+ Art = Types::Integer.enum(
67
+ 0 => 'leistung',
68
+ 1 => 'medikament',
69
+ 2 => 'labo',
70
+ 3 => 'diverse',
71
+ )
72
+
73
+ # Datum der Leistung
74
+ attribute :datum, Types::Strict::Date
75
+ # Anzahl der Leistung
76
+ attribute :anzahl, Types::Strict::Integer
77
+ # Zahnnummer der Leistung, Freie Eingabe des Zahnarztes.
78
+ attribute :zahnnummer, Types::Strict::String.optional.default(nil)
79
+ # Art der Leistung (0=Leistung, 1=Medikament, 2=Labo, 3=Diverse)
80
+ attribute :art, Art
81
+ # Nummer der Leistung
82
+ attribute :nummer, Types::Strict::String.optional.default(nil)
83
+ # Bezeichnung in der Sprache der Rechnung
84
+ attribute :bezeichnung, Types::Strict::String
85
+ # Ansatz der Leistung
86
+ attribute :ansatz, Types::Strict::Float.optional.default(nil)
87
+ # Punkte der Leistung
88
+ attribute :punkte, Types::Strict::Float.optional.default(nil)
89
+ # Auf der Leistung ist ein Rabatt gewährt (0 = ohne Rabatt; 1 = mit Rabatt)
90
+ attribute :rabatt, Types::Strict::Bool.default(false)
91
+ # Betrag
92
+ attribute :betrag, Types::Strict::Float
93
+
94
+ # Bruttobetrag (Anzahl * Betrag)
95
+ def total
96
+ anzahl * betrag
97
+ end
98
+ end
99
+
100
+ class Invoice < Struct
101
+ BehandlungStatus = Types::Integer.enum(
102
+ 0 => 'laufend',
103
+ 1 => 'abgeschlossen',
104
+ )
105
+ BehandlungTyp = Types::String.enum(
106
+ 'PP',
107
+ 'VS',
108
+ 'IV',
109
+ 'KK',
110
+ )
111
+ Sprache = Types::Integer.enum(
112
+ 1 => 'DE',
113
+ 2 => 'FR',
114
+ 3 => 'IT',
115
+ 4 => 'EN',
116
+ )
117
+
118
+ # Rechnungsnummer aus dem Programm
119
+ attribute :nummer, Types::Strict::Integer
120
+ # Datum der Rechnung (kann auch vordatiert sein)
121
+ attribute :datum, Types::Strict::Date
122
+ # Behandlungsbeginn
123
+ attribute :behandlungsbeginn, Types::Strict::Date
124
+ # Behandlungsende
125
+ attribute :behandlungsende, Types::Strict::Date
126
+ # 0=Behandlung laufend, 1=Behandlung abgeschlossen
127
+ attribute :behandlungstatus, BehandlungStatus
128
+ # Typ der Behandlung (PP / VS / IV / KK)
129
+ attribute :behandlungstyp, BehandlungTyp
130
+ # Sprache der Rechnung (1=deu, 2=franz. 3=ital. 4=engl.)
131
+ attribute :sprache, Sprache
132
+ # Rechnungsüberschrift
133
+ attribute :rech_titel, Types::Strict::String.optional.default(nil)
134
+ # Rechnungstext pro Zeile ein Eintrag
135
+ attribute :rech_text, Types::Strict::String.optional.default(nil)
136
+ # Versicherungsnummer
137
+ attribute :versicherungsnummer, Types::Strict::String.optional.default(nil)
138
+ # Rabatt in Prozent auf den markierten Leistungen
139
+ attribute :rabatt, Types::Strict::Integer.optional.default(nil)
140
+ # Datum der Verfügung
141
+ attribute :verfugungs_datum, Types::Strict::Date.optional.default(nil)
142
+ # Nummer der Verfügung
143
+ attribute :verfugungs_nummer, Types::Strict::Integer.optional.default(nil)
144
+ # Anhang
145
+ attribute :anhange, Types::Strict::Array.of(Types::Strict::String).default([].freeze)
146
+
147
+ # Rechnungsbetrag (ohne Vorauszahlungen)
148
+ attribute :total_rabatt, Types::Strict::Float
149
+ # Total aller Vorauszahlungen dieser Rechnung
150
+ attribute :total_vz, Types::Strict::Float
151
+
152
+ attribute :empfanger, Recipient.optional.default(nil)
153
+ attribute :patient, Patient
154
+ attribute :leistungen, Types::Strict::Array.of(InvoiceItem)
155
+
156
+ def total_punkte
157
+ leistungen.select{|l| l.punkte != nil }.map(&:punkte).reduce(0.0, :+)
158
+ end
159
+
160
+ # Total von allen Leistungen
161
+ def total_leis
162
+ leistungen.select{|l| l.art == 0 }.map(&:total).reduce(0.0, :+)
163
+ end
164
+
165
+ # Rechnungsbetrag (ohne Vorauszahlungen)
166
+ def total_med
167
+ leistungen.select{|l| l.art == 1 }.map(&:total).reduce(0.0, :+)
168
+ end
169
+
170
+ # Rechnungsbetrag (ohne Vorauszahlungen)
171
+ def total_labor
172
+ leistungen.select{|l| l.art == 2 }.map(&:total).reduce(0.0, :+)
173
+ end
174
+
175
+ # Rechnungsbetrag (ohne Vorauszahlungen)
176
+ def total_diverse
177
+ leistungen.select{|l| l.art == 3 }.map(&:total).reduce(0.0, :+)
178
+ end
179
+
180
+ # Total minus Vorauszahlungen dieser Rechnung
181
+ def total_netto
182
+ total_leis + total_med + total_labor + total_diverse + total_rabatt
183
+ end
184
+
185
+ # Rechnungsbetrag (ohne Vorauszahlungen)
186
+ def rechnungsbetrag
187
+ total_netto + total_vz
188
+ end
189
+
190
+ def to_h
191
+ h = {
192
+ RechNr: nummer,
193
+ RechDat: datum.strftime('%d.%m.%Y'),
194
+ BehBeginn: behandlungsbeginn.strftime('%d.%m.%Y'),
195
+ BehEnde: behandlungsende.strftime('%d.%m.%Y'),
196
+ BehStatus: behandlungstatus,
197
+ BehTyp: behandlungstyp,
198
+ RechBetrag: rechnungsbetrag,
199
+ TotalLeis: total_leis,
200
+ TotalMed: total_med,
201
+ TotalLabor: total_labor,
202
+ TotalDiverse: total_diverse,
203
+ TotalRabatt: total_rabatt,
204
+ TotalVZ: total_vz,
205
+ TotalNetto: total_netto,
206
+ Sprache: sprache,
207
+ PatNr: patient.nummer,
208
+ PatAnrede: fmt_string(patient.anrede, max: 4),
209
+ PatTitel: fmt_string(patient.titel, max: 20),
210
+ PatName: fmt_string(patient.name, max: 40),
211
+ PatVorname: fmt_string(patient.vorname, max: 40),
212
+ PatStrasse: fmt_string(patient.strasse, max: 30),
213
+ PatZusatz: fmt_string(patient.zusatz, max: 20),
214
+ PatLand: fmt_string(patient.land, max: 3),
215
+ PatPLZ: fmt_string(patient.plz, max: 10),
216
+ PatOrt: fmt_string(patient.ort, max: 40),
217
+ PatGebDatum: fmt_date(patient.geburtsdatum),
218
+ }
219
+ h[:RechTitel] = rech_titel[0..180] unless rech_titel.nil?
220
+ h[:VersNr] = versicherungsnummer unless versicherungsnummer.nil?
221
+ h[:Rabatt] = rabatt unless rabatt.nil?
222
+ h[:VerfügungsDat] = verfugungs_datum unless verfugungs_datum.nil?
223
+ h[:VerfügungsNr] = verfugungs_nummer unless verfugungs_nummer.nil?
224
+ if empfanger
225
+ h[:EmpfNr] = empfanger.nummer
226
+ h[:EmpfArt] = fmt_string(empfanger.art, max: 20)
227
+ h[:EmpfAnrede] = fmt_string(empfanger.anrede, max: 4)
228
+ h[:EmpfTitel] = fmt_string(empfanger.titel, max: 20)
229
+ h[:EmpfName] = fmt_string(empfanger.name, max: 40)
230
+ h[:EmpfVorname] = fmt_string(empfanger.vorname, max: 40)
231
+ h[:EmpfStrasse] = fmt_string(empfanger.strasse, max: 20)
232
+ h[:EmpfLand] = fmt_string(empfanger.land, max: 3)
233
+ h[:EmpfPLZ] = fmt_string(empfanger.plz, max: 10)
234
+ h[:EmpfOrt] = fmt_string(empfanger.ort, max: 40)
235
+ h[:EmpfGebDatum] = fmt_date(empfanger.geburtsdatum)
236
+ end
237
+ anhange.each.with_index(1) do |a, i|
238
+ # Path to attachment file
239
+ h["Anhang#{i}"] = a
240
+ end
241
+ rech_text.scan(/.{1,180}/).each.with_index(1) do |s, i|
242
+ # A line will go up to 180 characters, but could be shorter in case of a
243
+ # new line or simply a short line.
244
+ h["RechText[#{i}]"] = s
245
+ end unless rech_text.nil?
246
+ leistungen.each.with_index(1) do |l, i|
247
+ # Total der Punkt von Ansatz
248
+ # h["TotalPkt[#{i}]"] = l.punkte || 0.0
249
+ # Ansatz für Punkte
250
+ # h["TotalAnsatz[#{i}]"] = l.ansatz || 0.0
251
+
252
+ h["LeisDatum[#{i}]"] = fmt_date(l.datum)
253
+ h["LeisAnz[#{i}]"] = fmt_amount(l.anzahl)
254
+ h["LeisZahnNr[#{i}]"] = l.zahnnummer
255
+ h["LeisArt[#{i}]"] = l.art
256
+ h["LeisPosNr[#{i}]"] = l.nummer
257
+ h["LeisBez[#{i}]"] = l.bezeichnung
258
+ h["LeisAnsatz[#{i}]"] = l.ansatz
259
+ h["LeisPkt[#{i}]"] = l.punkte
260
+ h["LeisRabatt[#{i}]"] = l.rabatt ? 1 : 0
261
+ h["LeisBetrag[#{i}]"] = l.betrag
262
+ h["LeisTotal[#{i}]"] = l.total
263
+ end
264
+ h
265
+ end
266
+ end
267
+
268
+ # Folgende Einträge sind 1x pro Datei vorhanden
269
+ class Header < Struct
270
+ # Programmname
271
+ attribute :programm, Types::Strict::String
272
+ # Programm Version
273
+ attribute :programm_version, Types::Strict::String
274
+ # Schnittstellenversion
275
+ attribute :version, Types::Strict::String
276
+ # Eindeutige Kundennummer bei Mediserv
277
+ attribute :kundennummer, Types::Strict::Integer
278
+ # Die Stapelnummer ist eine fortlaufende Nummer. Für jede Datei eine neue Nummer
279
+ attribute :stapelnummer, Types::Strict::Integer
280
+
281
+ def to_h
282
+ {
283
+ Programm: programm,
284
+ ProgrammVersion: programm_version,
285
+ Version: version,
286
+ KuNr: kundennummer,
287
+ StapelNr: stapelnummer,
288
+ }
289
+ end
290
+ end
291
+
292
+ class Batch < Struct
293
+ attribute :allgemein, MediServ::API::Header
294
+ attribute :rechnungen, Types::Strict::Array.of(MediServ::API::Invoice)
295
+ attribute :created_at, Types::Strict::DateTime
296
+
297
+ # Anzahl Rechnungen
298
+ def anzahl_rechnungen
299
+ rechnungen.length
300
+ end
301
+
302
+ # Gesamtsumme Taxpunkte
303
+ def total_pkt
304
+ rechnungen.map(&:total_punkte).reduce(0.0, :+)
305
+ end
306
+
307
+ # Gesamtsumme aller Leistungen
308
+ def total_leis
309
+ rechnungen.map(&:total_leis).reduce(0.0, :+)
310
+ end
311
+
312
+ # Gesamtsumme aller Medikamente
313
+ def total_med
314
+ rechnungen.map(&:total_med).reduce(0.0, :+)
315
+ end
316
+
317
+ # Gesamtsumme aller Laborkosten
318
+ def total_labor
319
+ rechnungen.map(&:total_labor).reduce(0.0, :+)
320
+ end
321
+
322
+ # Gesamtsumme Diverse Positionen
323
+ def total_diverse
324
+ rechnungen.map(&:total_diverse).reduce(0.0, :+)
325
+ end
326
+
327
+ # Gesamtsumme Rabatte
328
+ def total_rabatt
329
+ rechnungen.map(&:total_rabatt).reduce(0.0, :+)
330
+ end
331
+
332
+ # Gesamtsumme der abgezogenen Vorauszahlungen
333
+ def total_vz
334
+ rechnungen.map(&:total_vz).reduce(0.0, :+)
335
+ end
336
+
337
+ # Gesamtsumme Total (ist auch die Summe von TotalLeis + TotalMed + TotalLabor + TotalDiverse + TotalRabatt)
338
+ def total
339
+ total_leis + total_med + total_labor + total_diverse + total_rabatt
340
+ end
341
+
342
+ # Nettosumme (Total – Vorauszahlung)
343
+ def total_netto
344
+ total - total_vz
345
+ end
346
+
347
+ def to_h
348
+ h = {
349
+ Allgemein: allgemein.to_h.merge({
350
+ Total: total,
351
+ TotalPkt: total_pkt,
352
+ TotalLeis: total_leis,
353
+ TotalMed: total_med,
354
+ TotalLabor: total_labor,
355
+ TotalDiverse: total_diverse,
356
+ TotalRabatt: total_rabatt,
357
+ TotalVz: total_vz,
358
+ TotalNetto: total_netto,
359
+ Datum: fmt_date(created_at),
360
+ Zeit: fmt_time(created_at),
361
+ AnzRech: anzahl_rechnungen,
362
+ }),
363
+ }
364
+ rechnungen.each.with_index(1) do |r,i|
365
+ h["R#{i}"] = r.to_h
366
+ end
367
+ stringify_keys h
368
+ end
369
+
370
+ def stringify_keys(h)
371
+ h.reduce({}) do |h, (k,v)|
372
+ v = stringify_keys(v) if v.kind_of?(Hash)
373
+ h.merge({k.to_s => v})
374
+ end
375
+ end
376
+ end
377
+
378
+ end
@@ -0,0 +1,6 @@
1
+ module MediServ::API
2
+ require 'mediserv/api/error'
3
+ require 'mediserv/api/types'
4
+ require 'mediserv/api/storage'
5
+ require 'mediserv/api/client'
6
+ end
data/lib/mediserv.rb ADDED
@@ -0,0 +1,3 @@
1
+ module MediServ
2
+ require 'mediserv/api'
3
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mediserv
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Denteo AG
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-10-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dry-types
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.5.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 1.5.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-struct
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 1.4.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.4.0
41
+ description: Ruby client interacting with MediServ
42
+ email: simon@denteo.ch
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - lib/mediserv.rb
48
+ - lib/mediserv/api.rb
49
+ - lib/mediserv/api/client.rb
50
+ - lib/mediserv/api/encoder.rb
51
+ - lib/mediserv/api/error.rb
52
+ - lib/mediserv/api/format.rb
53
+ - lib/mediserv/api/storage.rb
54
+ - lib/mediserv/api/types.rb
55
+ homepage: https://rubygems.org/gems/mediserv
56
+ licenses:
57
+ - MIT
58
+ metadata: {}
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubygems_version: 3.1.2
75
+ signing_key:
76
+ specification_version: 4
77
+ summary: Ruby client interacting with MediServ
78
+ test_files: []