<resource schema="flashheros">
	<meta name="title">Flash/Heros Heidelberg spectra</meta>
	<meta name="creationDate">2012-04-03T13:30:00</meta>
	<meta name="schema-rank">100</meta>
	<meta name="description">
		Spectra from the Flash and Heros Echelle spectrographs developed
		at Landessternwarte Heidelberg and mounted at La Silla and various
		other observatories.  The data
		mostly contains spectra of OB stars.  Heros was the name of the
		instrument after Flash got a second channel in 1995.
	</meta>
	<meta name="doi">10.21938/oUuqajnWmofP2w75.Acc8g</meta>
	<meta name="copyright">If you use this data, please acknowledge:
		"This research made use of Flash/Heros data served through
		the GAVO data center".  Thanks.</meta>
	<meta name="creator">Wolf, B.; Kaufer, A.; Mandel, H.;
		Stahl, O.</meta>

	<meta name="subject">spectroscopy</meta>
	<meta name="subject">ob-stars</meta>

	<meta name="source">1996A&amp;A...312..539S</meta>
	<meta name="facility">La Silla, primarily the ESO 50cm telescope,
		and others</meta>
	<meta name="instrument">Flash/Heros</meta>
	<meta name="coverage.waveband">Optical</meta>
	<meta name="data.type">Archive</meta>

	<meta>
  	date: 2023-11-08
  	date.role: ExportRequested
	</meta>

	<table id="data" onDisk="true" adql="True">
		<meta name="title">Flash/Heros SSA table</meta>

		<FEED source="//scs#splitPosIndex"
			long="degrees(long(ssa_location))" lat="degrees(lat(ssa_location))"/>
		<FEED source="//ssap#obscore-time-index"/>
		<index columns="ssa_specstart"/>
		<index columns="ssa_specend"/>

		<mixin
			fluxUnit=" "
			spectralUnit="0.1nm"
			instrument="Flash/Heros"
			collection="flashheros"
			dataSource="pointed"
			creationType="archival"
			fluxCalibration="UNCALIBRATED"
			spectralCalibration="ABSOLUTE"
			fluxUCD="phot.flux.density"
			spectralUCD="em.wl"
			statSpectError="0.05"
			spectralResolution="2.5e-11"
		>//ssap#hcd</mixin>
		<mixin
			calib_level="1"
			em_res_power="14000"
			facility_name="'ESO La Silla'"
			coverage="ssa_region">//obscore#publishSSAPHCD</mixin>
		<mixin>//ssap#simpleCoverage</mixin>

		<meta name="_associatedDatalinkService">
			<meta name="serviceId">sdl</meta>
			<meta name="idColumn">ssa_pubDID</meta>
		</meta>
		<column name="localKey" type="text"
			ucd="meta.id"
			tablehead="Key"
			description="Local observation key (used to connect to single orders)."
			verbLevel="1"/>
		<column name="ssa_fluxcalib" type="text"
			utype="ssa:Char.FluxAxis.Calibration"
			tablehead="Calib Flux" verbLevel="20"
			description="Type of flux calibration">
			<values original="//ssap#flux_calib_values"/>
		</column>
	</table>

	<coverage>
		<updater sourceTable="data"/>
		<spectral>2.15857e-19 6.11101e-19</spectral>
		<temporal>47847.2 51370.2</temporal>
		<spatial>6/90,472,1198,1460,1504,1558,1748,2851,2884,3030,3138,3198,3250,4419,9054,9836,11959,12551,12604,12865,13721,14008,14108,14197,14280,14302,14400,14431,14630-14631,14646,14690,14711,14777,14855,14906,15135,15375,15392,15502,15631,15659,15672,15747,15749,16144,17487,18688,18856,19017,19045,20523,20536,20764,20940,21004,21324,21401,21439,21447,21515,21530,21879,22254,22517,22615,22787,22805,22824,22854,22862,22911,23081,23611,23621,23637,23963,24048,24179,24504,24839,25487,25659,25789,25950,25973,26879,27574,28711,28804,28816,28890,28921,29089,29116,29162,29207,29513,29519,29893,30722,30818,30859,31284,31759,31779,33094,33098,33100,33121,33190,33361,34054,34526,34896,37105,37108,37164,37253,37330,37339,37343,37364,37729,37779,37877,37898,37904,37915,37922,37926,37930,38016,38018,38024,38049,39209,39216,39233,39236,39281,39327,39486,39671,39831,39853,39863,39877,39923,39937,41122,41171,41270,41459,41534,41630,41738,41763,41939,41982,42010,42046,42080,42221,42273,42279,42333-42334,42383,42394,42416,42464,42611,42623,42652,42818,42948,42956,43183,43294,43365,43407,43494,43531,43689,44306,44312,44451,44502,44903,46081,47056,47121,47353,48534</spatial>
	</coverage>

	<STREAM id="localKeyDef">
		<var name="localKey">"/".join(
			\\inputRelativePath.split("/")[2:])</var>
	</STREAM>

	<STREAM id="herosPrimaryHeaderFuncs">
				<apply name="getFoV">
					<!-- this is according to a mail sent by Otmar Stahl on 2012-04-13:

Das Feld, das die Faser jeweils am Himmel gesehen hat, herauszufinden, war
mehr Arbeit, weil der Spektrograph an etlichen Teleskopen benutzt wurde.
Hier ist meine Liste:

Teleskope:      Brennweite      FOV in " (100 µ fiber)
=======================================================
Heidelberg/Zeiss      6 m       3.4  lsw89/90
Heidelberg/Waltz     14 m       1.5  andere Jahre
Calar Alto 2.2m      17.6       1.2  ca
ESO 50 cm             7.5 m     2.7  ls
Tautenburg           21 m       1.0  taut (focal reducer benutzt?)
ESO 1.5m             23 m       0.9  ls99 (focal reducer benutzt?)

In Tautenburg und an ESO 1.5m wurde, wenn ich mich richtig erinnere, ein
Fokalreduktor benutzt, damit das Feld größer wurde. An dessen
Spezifikationen kann ich mich aber nicht mehr erinnern. -->
					<setup>
						<par name="fovByDir">{
							"ca90": 17.6,
							"ca92": 17.6,
							"ca98": 17.6,
							"caaug91": 17.6,
							"caoct91": 17.6,
							"ls92": 2.7,
							"ls93": 2.7,
							"ls94": 2.7,
							"ls95": 2.7,
							"ls96": 2.7,
							"ls96MJ": 2.7,
							"ls97": 2.7,
							"ls99": 2.7,
							"lsw8990": 3.4,
							"lsw91": 1.5,
							"lsw92": 1.5,
							"lsw95": 1.5,
							"lsw97": 1.5,
							"taut": 1.0,}
						</par>
					</setup>
					<code>
						dirName = \\inputRelativePath.split("/")[2]
						vars["fov"] = fovByDir[dirName]
					</code>
				</apply>

				<var name="preObject"
					>re.sub(",.*", "", @OBJECT).strip() or "unknown"</var>

				<apply name="cleanObject" procDef="//procs#mapValue">
					<bind name="destination">"cleanedObject"</bind>
					<bind name="failuresMapThrough">True</bind>
					<bind name="value">@preObject</bind>
					<bind name="sourceName">"flashheros/res/namefixes.txt"</bind>
				</apply>

				<apply name="classifyObject">
					<doc>
						adds an objClass var based on the value of the object
						header.  Instrument frames are identified here, and their
						processing is cut short.
					</doc>
					<setup>
						<par name="objClasses"> {
							"Mars": "planet",
							"ThAr": "instrument", # Thorium-Argon standard spectrum
							"WLC1": "instrument",
							"WLC2": "instrument",
							"FF": "instrument",
							"FFU": "instrument",
							"Dark0": "instrument",
							"Bias": "instrument",
						}
						</par>
					</setup>
					<code>
						vars["objClass"] = objClasses.get(@OBJECT, "star")
						if @OBJECT=="Mars":
							vars["objClass"] = 'planet'
						if vars["objClass"]=="instrument":
							raise IgnoreThisRow()
					</code>
				</apply>

				<apply procDef="//procs#resolveObject">
					<bind name="identifier">@cleanedObject</bind>
					<bind name="ignoreUnknowns">True</bind>
				</apply>

				<apply name="inferDateObs">
					<doc>
						The observation date is coded in a somewhat unstable way.
						DATE-OBS sometimes is a d/m/y-date, sometimes some whacko
						float inserted by MIDAS.  Sometimes, there is an MJD-OBS
						(probably left by MIDAS), which then mostly looks good.
						Sometimes, there's neither.

						This apply tries to untangle the mess and leaves a timestamp
						or None for dateObs in dateObsTS.
					</doc>
					<code>
						if "MJD_OBS" in vars:
							vars["dateObsTS"] = mjdToDateTime(@MJD_OBS)
						elif "DATE_OBS" in vars:
							try:
								vars["dateObsTS"] = parseDefaultDatetime(@DATE_OBS)
							except ValueError:
								vars["dateObsTS"] = parseDate(@DATE_OBS, "%d/%m/%y")
						else:
							vars["dateObsTS"] = None
					</code>
				</apply>

				<apply name="parseProcDate">
					<code>
						try:
							vars["cdate"] = parseDate(@DATE, "%d/%m/%y")
						except ValueError:
							vars["cdate"] = parseDefaultDatetime(@DATE)
					</code>
				</apply>
	</STREAM>


	<data id="import">
		<property key="previewDir">previews</property>
		<sources pattern="data/*.mt" recurse="True"/>
		<fitsProdGrammar>
			<rowfilter procDef="//products#define">
				<bind name="table">"\schema.data"</bind>
				<bind name="mime">"application/fits"</bind>
				<bind name="preview_mime">"image/png"</bind>
				<bind name="preview">\standardPreviewPath</bind>
			</rowfilter>
			<rowfilter name="addSDM">
				<code>
					yield row
					baseAccref = os.path.splitext(row["prodtblPath"])[0]
					row["prodtblAccref"] = baseAccref+".vot"
					row["prodtblPath"] = \fullDLURL{sdl}
					row["prodtblMime"] = "application/x-votable+xml"
					yield row
				</code>
			</rowfilter>
		</fitsProdGrammar>

		<make table="data">
			<rowmaker id="make_data" idmaps="ssa_*, localKey">
				<FEED source="herosPrimaryHeaderFuncs"/>
				<FEED source="localKeyDef"/>
				<var name="specMin">(@CRVAL1+(1-@CRPIX1)*@CDELT1)*1e-10</var>
				<var name="specMax">(@CRVAL1+(@NAXIS1-@CRPIX1)*@CDELT1)*1e-10</var>

				<apply><code>
					@dstitle = "Flash/Heros %s %s"%(@cleanedObject, @dateObsTS)
					if @dstitle=='Flash/Heros unknown None':
						# no obs date, no object, and hence no position: there is
						# no way these could ever make sense to anyone, but they
						# will always look odd.  Let's skip them
						raise IgnoreThisRow("too little metadata")
				</code></apply>

				<apply procDef="//ssap#setMeta" name="setSSAPMeta">
					<bind name="dstitle">@localKey</bind>
					<bind name="pubDID">\standardPubDID</bind>
					<bind name="length">@NAXIS1</bind>
					<bind name="specext">@specMax-@specMin</bind>
					<bind name="specend">@specMax</bind>
					<bind name="specstart">@specMin</bind>
					<bind name="specmid">(@specMax+@specMin)/2.</bind>
					<bind name="targname">@cleanedObject</bind>
					<bind name="cdate">@cdate</bind>
					<bind name="dateObs">@dateObsTS</bind>
					<bind name="targclass">"star"</bind>
					<bind name="dstitle">@dstitle</bind>
					<bind name="alpha">@simbadAlpha</bind>
					<bind name="delta">@simbadDelta</bind>
					<bind name="aperture">@fov</bind>
				</apply>

				<map key="ssa_fluxcalib">@FLUXCAL</map>
			</rowmaker>
		</make>
	</data>

	<table id="ordersmeta" onDisk="True" adql="True">
		<meta name="description">SSA metadata for split-order
			Flash/Heros Echelle spectra</meta>
		<meta name="_associatedDatalinkService">
			<meta name="serviceId">echdl</meta>
			<meta name="idColumn">ssa_pubDID</meta>
		</meta>
		<mixin
			fluxUnit=" "
			spectralUnit="0.1nm"
			instrument="Flash/Heros"
			dataSource="pointed"
			creationType="archival"
			fluxCalibration="UNCALIBRATED"
			spectralCalibration="ABSOLUTE"
			fluxUCD="phot.flux.density"
			spectralUCD="em.wl"
			statSpectError="0.05"
			spectralResolution="2.5e-11"
		>//ssap#hcd</mixin>
		<column name="localKey" type="text"
			ucd="meta.id"
			tablehead="Key"
			description="Local observation key."
			verbLevel="1"/>
		<FEED source="//echelle#ssacols"/>
	</table>

	<data id="import_ordersmeta">
		<sources id="raw_sources" pattern="data_raw/n*mt" recurse="True"/>
		<fitsProdGrammar>
			<rowfilter procDef="//products#define">
				<bind name="table">"\schema.ordersmeta"</bind>
				<bind name="mime">base.votableType</bind>
				<bind name="path">\fullDLURL{echdl}</bind>
			</rowfilter>
		</fitsProdGrammar>
		<make table="ordersmeta">
			<rowmaker idmaps="*">
				<FEED source="herosPrimaryHeaderFuncs"/>
				<FEED source="localKeyDef"/>

				<apply name="getJointProperties">
					<code>
						grammar = rd.getById("midasgrammar")
						subordinate = grammar.parse(@parser_.sourceToken)
						vars.update(subordinate.getOrdersMeta())
					</code>
				</apply>

				<apply name="fixMidasUnits">
					<code>
						@specMax /= 1e10
						@specMin /= 1e10
					</code>
				</apply>
				
				<apply procDef="//echelle#setSSAMeta">
					<bind name="n_orders">@n_orders</bind>
					<bind name="order_min">@order_min</bind>
					<bind name="order_max">@order_max</bind>
				</apply>

				<apply procDef="//ssap#setMeta" name="setSSAPMeta">
					<bind name="dstitle">@localKey</bind>
					<bind name="pubDID">\standardPubDID</bind>
					<bind name="length">@total_length</bind>
					<bind name="specext">@specMax-@specMin</bind>
					<bind name="specend">@specMax</bind>
					<bind name="specstart">@specMin</bind>
					<bind name="specmid">(@specMax+@specMin)/2.</bind>
					<bind name="targname">@cleanedObject</bind>
					<bind name="cdate">@cdate</bind>
					<bind name="dateObs">@dateObsTS</bind>
					<bind name="targclass">"star"</bind>
					<bind name="dstitle"
						>"Flash/Heros Split Orders %s %s"%(
							@cleanedObject, @dateObsTS)</bind>
					<bind name="alpha">@simbadAlpha</bind>
					<bind name="delta">@simbadDelta</bind>
					<bind name="aperture">@fov</bind>
				</apply>
			</rowmaker>
		</make>
	</data>

	<table id="spectrum">
		<mixin ssaTable="data"
			fluxDescription="Flux F(lambda)dlambda"
			spectralDescription="Wavelength"
			>//ssap#sdm-instance</mixin>
	</table>

	<data id="echelle_orders" auto="False">
		<customGrammar module="res/midasgrammar" id="midasgrammar"
			isDispatching="True"/>

		<table id="order_instance">
			<!-- these are the individual orders of an Echelle spectrum -->
			<mixin ssaTable="ordersmeta"
				fluxDescription="Flux F(lambda)dlambda"
				spectralDescription="Wavelength"
				>//ssap#sdm-instance</mixin>
			<param name="order" type="integer"
				ucd="instr.order"
				utype="spec:Spectrum.Data.SpectralAxis.order"
				description="Echelle order"/>
		</table>

		<make id="ordermake_template" table="order_instance">
			<!-- template for the rowmaker of the individual spectum instances
			within an echelle_orders; these need a role before they work -->
			<rowmaker>
				<simplemaps>spectral:lambda,flux:flux</simplemaps>
			</rowmaker>
		</make>
	</data>

	<data id="build_sdm_data" auto="False">
		<embeddedGrammar>
			<iterator>
				<setup>
					<code>
						from gavo.protocols import products
						from gavo.utils import pyfits
					</code>
				</setup>
				<code>
				fitsPath = os.path.join(
					base.getConfig("inputsDir"),
					os.path.splitext(self.sourceToken["accref"])[0]+".mt")
				hdu = pyfits.open(fitsPath)[0]

				crval1, crpix1 = hdu.header["CRVAL1"], hdu.header["CRPIX1"]
				cdelt1 = hdu.header["CDELT1"]

				def specTrans(pixNo):
					return crval1+(pixNo+1-crpix1)*cdelt1

				for spec, flux in enumerate(hdu.data):
					yield {"spectral": specTrans(spec), "flux": flux}
				</code>
			</iterator>
		</embeddedGrammar>
		<make table="spectrum">
			<parmaker>
				<apply procDef="//ssap#feedSSAToSDM"/>
			</parmaker>
		</make>
	</data>

	<service id="sdl" allowed="dlget,dlmeta">
		<meta name="title">Flash/Heros Datalink Service</meta>
		<datalinkCore>
			<descriptorGenerator procDef="//soda#sdm_genDesc">
				<bind name="ssaTD">"\rdId#data"</bind>
			</descriptorGenerator>
			<dataFunction procDef="//soda#sdm_genData">
				<bind name="builder">"\rdId#build_sdm_data"</bind>
			</dataFunction>
			<FEED source="//soda#sdm_plainfluxcalib"/>
			<FEED source="//soda#sdm_cutout"/>
			<FEED source="//soda#sdm_format"/>
			<metaMaker semantics="#progenitor">
				<code>
					if descriptor.pubDID is None:
						return

					# see README for mapping between split and merged order
					splitId = re.sub(r"f(\d+.mt)$", r"n\1",
						descriptor.pubDID.replace("/data/", "/data_raw/"))

					yield descriptor.makeLink(
						rd.getById("echdl").getURL("dlmeta", ID=splitId),
						contentType=base.votableType+";content=datalink",
						description="Split Echelle Orders")
				</code>
			</metaMaker>
		</datalinkCore>
	</service>

	<service id="echdl" allowed="dlget,dlmeta">
		<meta name="title">Flash/Heros Split-Order Datalink Service</meta>
		<datalinkCore>
			<descriptorGenerator procDef="//soda#sdm_genDesc">
				<bind name="ssaTD">"\rdId#ordersmeta"</bind>
			</descriptorGenerator>

			<metaMaker semantics="#derivation">
				<code>
					if descriptor.pubDID is None:
						return

					# see README for mapping between split and merged order
					splitId = re.sub(r"n(\d+.mt)$", r"f\1",
						descriptor.pubDID.replace("/data_raw/", "/data/"))

					yield descriptor.makeLink(
						rd.getById("sdl").getURL("dlmeta", ID=splitId),
						contentType=base.votableType+";content=datalink",
						description="Merged Order Spectrum")
				</code>
			</metaMaker>

			<dataFunction>
				<code>
					descriptor.data = {
						"input_path": os.path.join(base.getConfig("inputsDir"),
							descriptor.accref),
						"order_min": descriptor.ssaRow["order_min"],
						"order_max": descriptor.ssaRow["order_max"],
						}
				</code>
			</dataFunction>
			
			<metaMaker>
				<code>
					res = MS(InputKey, name="ORDER",
						type="smallint[2]", ucd="instr.order",
						tablehead="Order", description="Echelle order",
						xtype="interval",
						values=MS(Values, min=descriptor.limits["order_min"].min,
							max=descriptor.limits["order_max"].max))
					res.setProperty("defaultForForm",
						"{} {}".format(descriptor.limits["order_min"].min,
							descriptor.limits["order_max"].max))
					yield res
				</code>
			</metaMaker>

			<dataFunction>
				<code>
					if args["ORDER"]:
						descriptor.data["order_min"] = args["ORDER"][0]
						descriptor.data["order_max"] = args["ORDER"][1]
				</code>
			</dataFunction>

			<dataFormatter>
				<setup>
					<code>
						from gavo import rsc
						from gavo.formats import votablewrite
					</code>
				</setup>
				<code>
					orders = [str(n) for n in range(descriptor.data["order_min"],
						descriptor.data["order_max"]+1)]
					tables = [rd.getById("order_instance").change(id="order-%s"%order)
						for order in orders]
					tableInd = dict(zip(orders, tables))

					dd = rd.getById("echelle_orders").change(
						tables=tables,
						makes=[rd.getById("ordermake_template").change(
								role=order, table=tableInd[order])
							for order in orders])

					d = rsc.makeData(dd, forceSource=descriptor.data)

					for t in d.tables.values():
						t.setParam("order", int(t.tableDef.id.split("-")[-1]))
						# meta on table is not copyable, so we need to fix the
						# utype manually
						t.setMeta("utype", "spec:Spectrum")

					return base.votableType, votablewrite.getAsVOTable(d)
				</code>
			</dataFormatter>
		</datalinkCore>
	</service>

	<service id="echssa" allowed="ssap.xml,form">
		<meta name="title">Flash/Heros Split-Order SSAP Service</meta>
		<meta name="shortName">F/H Orders SSAP</meta>
		<meta name="ssap.dataSource">pointed</meta>
		<meta name="ssap.testQuery">MAXREC=1</meta>
		<meta name="ssap.creationType">archival</meta>
		<meta name="ssap.complianceLevel">query</meta>
		<meta name="_related" title="Flash/Heros Joined Spectra"
			>\internallink{flashheros/q/ssa/info}</meta>

		<publish sets="ivo_managed" render="ssap.xml"/>

		<ssapCore queriedTable="ordersmeta">
			<property name="previews">auto</property>
			<FEED source="//ssap#hcd_condDescs"/>
		</ssapCore>
	</service>
	
	<service id="web" defaultRenderer="form">
		<meta name="shortName">Flash/Heros Web</meta>
		<meta name="title">Flash/Heros Spectra Web Interface</meta>
		<meta name="_related" title="Flash/Heros SSAP"
			>\internallink{flashheros/q/ssa/info}</meta>
	
		<dbCore queriedTable="data">
			<condDesc>
				<inputKey original="ssa_targname" showItems="10"
						multiplicity="multiple">
					<values fromdb=
						"ssa_targname from flashheros.data order by ssa_targname"/>
				</inputKey>
			</condDesc>
			<condDesc buildFrom="ssa_location"/>
			<condDesc buildFrom="ssa_dateObs"/>
			<condDesc>
				<inputKey original="mime">
					<property name="defaultForForm">application/x-votable+xml</property>
					<values>
						<option title="SDM VOTable">application/x-votable+xml</option>
						<option title="1D FITS image">application/fits</option>
					</values>
				</inputKey>
			</condDesc>
			
			<condDesc>
				<inputKey type="file" name="upload"
					description="Upload a list of target positions; this expects
						 a text file with three floats per line (ICRS RA, Dec, radius,
						 all in decimal degrees)."/>
				<phraseMaker>
					<code>
						circles = []
						for ln in inPars[inputKeys[0].name][1].readlines():
							ra, dec, sr = map(float, ln.split()[:3])
							circles.append(pgsphere.SCircle(
								pgsphere.SPoint.fromDegrees(ra, dec),
								sr*utils.DEG))
						
						yield " OR ".join(
							"ssa_location @ %%(%s)s"%base.getSQLKey("roi", c, outPars)
							for c in circles)
					</code>
				</phraseMaker>
			</condDesc>
		</dbCore>

		<outputTable>
			<outputField original="accref"/>
			<outputField original="ssa_targname"/>
			<FEED source="//ssap#atomicCoords"/>
			<outputField original="ssa_dateObs"/>
			<outputField original="ssa_specstart" displayHint="displayUnit=Angstrom"/>
			<outputField original="ssa_specend" displayHint="displayUnit=Angstrom"/>
			<outputField original="ssa_aperture"/>
			<outputField original="mime"/>
			<outputField name="datalink" type="text"
				tablehead="Datalink"
				description="Link to a datalink document containing additional
					access, options, ancillary data, etc."
				select="'\getConfig{web}{serverURL}/\rdId/sdl/dlmeta?ID='||ssa_pubdid"
				displayHint="type=url">
				<property name="targetType"
					>application/x-votable+xml;content=datalink</property>
				<property name="targetTitle">Datalink</property>
			</outputField>
		</outputTable>
	</service>

	<service id="ssa" allowed="form,ssap.xml" defaultRenderer="ssap.xml">
		<meta name="shortName">Flash/Heros SSAP</meta>
		<meta name="title">Flash/Heros SSAP</meta>
		<meta name="ssap.dataSource">pointed</meta>
		<meta name="ssap.testQuery">TARGETNAME=bet%20Ori</meta>
		<meta name="ssap.creationType">archival</meta>
		<meta name="ssap.complianceLevel">query</meta>
		<meta name="_related" title="Flash/Heros Web interface"
			>\internallink{flashheros/q/web/info}</meta>
		<publish render="ssap.xml" sets="ivo_managed"/>
		<publish render="form" sets="ivo_managed,local" service="web"/>

		<ssapCore queriedTable="data">
			<FEED source="//ssap#hcd_condDescs"/>
			<outputTable verbLevel="30"/>
			<property name="previews">auto</property>
		</ssapCore>
	</service>

	<regSuite>
		<regTest title="Flash/Heros SSA queryData looks ok">
			<url targetname="NOVA Cir 1995" RESPONSEFORMAT="votabletd"
				request="queryData"
				FORMAT="compliant" VERB="3">ssa</url>
			<code>
				self.assertXpath("//v:INFO[@ucd='meta.bib.bibcode']", {
					"value": "1996A&amp;A...312..539S"})
				self.assertXpath("//v:TABLEDATA/v:TR[1]/v:TD[16]", {
					None: "NOVA Cir 1995"})
			</code>
		</regTest>

		<regTest title="Flash/Heros SDM VOT generation works">
			<url httpHonorRedirects="True" FORMAT="votabletd"
				>/getproduct/flashheros/data/ls95/blue/f0044.vot</url>
			<code>
				self.assertXpath("//v:PARAM[@name='ssa_cdate']", {
					"value": "1998-07-27T00:00:00"})
				row = self.getFirstVOTableRow(rejectExtras=False)
				self.assertEqual(row["spectral"], 3421.30005098)
				self.assertEqual(row["flux"], 0.0)
			</code>
		</regTest>

		<regTest title="SDM datalink generation works.">
			<url ID="ivo://org.gavo.dc/~?flashheros/data/ls95/blue/f0044.vot"
				BAND="5e-7 5.3e-7" FORMAT="application/fits"
					>sdl/dlget</url>
			<code>
				self.assertHasStrings("OBJECT  = 'NOVA Cir 1995'",
					"@\\0\\0\\0@")
			</code>
		</regTest>

		<regTest title="ecorder datalink serves order parameters">
			<url ID="ivo://org.gavo.dc/~?flashheros/data_raw/ls95/blue/n0043.mt"
				>echdl/dlmeta</url>
			<code>
				self.assertXpath(
					'//v:PARAM[@name="ORDER"]/v:VALUES/v:MIN',
					{'value': "103"})
			</code>
		</regTest>

		<regTest title="ecorder heros datalink honors order parameters">
			<url ID="ivo://org.gavo.dc/~?flashheros/data_raw/ls95/blue/n0043.mt"
				ORDER="112 114"
				>echdl/dlget</url>
			<code>
				self.assertHasStrings("order-112", "order-113", "order-114")
				self.assertLacksStrings("order-111", "order-115")
				self.assertHasStrings('encoding="base64">QLO')
			</code>
		</regTest>

		<regTest title="ecorder flash datalink honors order parameters">
			<url ID="ivo://org.gavo.dc/~?flashheros/data_raw/ca92/n0006.mt"
				ORDER="87 89"
				>echdl/dlget</url>
			<code>
				self.assertHasStrings("order-87", "order-88", "order-89")
				self.assertLacksStrings("order-86", "order-90")
				self.assertHasStrings(' encoding="base64">QLmCsWAAAA')
			</code>
		</regTest>

		<regTest title="flashheros datalink handles normalized and uncalibrated
				spectra">
			<url
				>sdl/dlmeta?ID=ivo%3A//org.gavo.dc/~%3F
					flashheros/data/ls99/red/f9241.mt
					&amp;ID=ivo%3A//org.gavo.dc/~%3F
					flashheros/data/ca92/f0006.mt</url>
			<code>
				self.assertXpath("//v:OPTION[@name='UNCALIBRATED']",
					{"value": "UNCALIBRATED"})
				self.assertXpath("//v:OPTION[@name='NORMALIZED']",
					{"value": "NORMALIZED"})
				self.assertXpath("//v:OPTION[@name='UNCALIBRATED']/.."
					"/v:OPTION[@name='RELATIVE']",
					{"value": "RELATIVE"})
				self.assertXpath("//v:OPTION[@name='NORMALIZED']/.."
					"/v:OPTION[@name='RELATIVE']",
					{"value": "RELATIVE"})

				# unrelated: see that there's a content qualifier
				self.assertXpath("//v:TR[v:TD[6]='#this' and v:TD[1]='ivo://org.gavo.dc/~?flashheros/data/ca92/f0006.mt']/v:TD[10]", {
					None: "#spectrum"})
			</code>
		</regTest>
	</regSuite>

	<regSuite title="DaCHS obscore datalink regression">
		<regTest title="Obscore datalink properly redirects">
			<url ID="ivo://org.gavo.dc/~?flashheros/data/ca92/f0006.vot"
				>/__system__/obscore/dl/dlmeta</url>
			<code>
				self.assertHTTPStatus(302)
				self.assertHeader("location",
					EqualingRE(r".*/flashheros/q/sdl/dlmeta\?ID=ivo%3A"
						"//org.gavo.dc/~%3Fflashheros/data/ca92/f0006.vot"))
			</code>
		</regTest>
		<regTest title="Obscore datalink returns basic document when source table
				has no datalink service.">
			<url ID="ivo://org.gavo.dc/~?emi/data/L0009_uniform.fits">/__system__/obscore/dl/dlmeta</url>
			<code>
				bySem = dict((r["semantics"], r)
					for r in self.getVOTableRows())
				self.assertTrue(bySem["#this"]["access_url"].endswith(
					"getproduct/emi/data/L0009_uniform.fits"))
			</code>
		</regTest>
	</regSuite>
</resource>
