File indexing completed on 2026-04-25 08:29:11
0001 """
0002 Tag parameter schemas — required and optional fields per tag type.
0003
0004 Extensible without migration: add fields here and they appear in forms and validation.
0005 The 'required' list is enforced on tag creation. The 'optional' list populates form fields.
0006 All values are stored as JSON in the tag's parameters field.
0007
0008 Processes and choices assembled from 26.02.0 campaign production pages:
0009 https://eic.github.io/epic-prod/FULL/26.02.0/
0010 https://eic.github.io/epic-prod/RECO/26.02.0/
0011 """
0012
0013 TAG_SCHEMAS = {
0014 'p': {
0015 'required': ['process', 'beam_energy_electron', 'beam_energy_hadron'],
0016 'optional': [
0017 'beam_species', 'q2_range',
0018 'decay_mode', 'hadron_charge', 'coherence', 'model', 'polarization',
0019 'notes',
0020 ],
0021 'label': 'Physics',
0022 'prefix': 'p',
0023 'model': 'PhysicsTag',
0024 'choices': {
0025 'process': [
0026 'DIS_NC', 'DIS_CC', 'DDIS',
0027 'DVCS', 'DDVCS',
0028 'SIDIS_D0',
0029 'DEMP', 'DVMP',
0030 'DIFFRACTIVE_JPSI', 'DIFFRACTIVE_PHI', 'DIFFRACTIVE_RHO',
0031 'PHOTOPRODUCTION_JPSI', 'UPSILON',
0032 ],
0033 'beam_energy_electron': ['5', '10', '18', 'N/A'],
0034 'beam_energy_hadron': ['41', '100', '110', '130', '250', '275', 'N/A'],
0035 'beam_species': ['ep', 'eHe3', 'eAu'],
0036 'q2_range': [
0037 'minQ2=1', 'minQ2=10', 'minQ2=100', 'minQ2=1000',
0038 'q2_0_10', 'q2_1_100', 'q2_1_1000',
0039 'q2_1to10', 'q2_1to50', 'q2_1to10000',
0040 'q2_2to10', 'q2_10to100', 'q2_100to10000',
0041 'q2_nocut',
0042 ],
0043 'decay_mode': ['edecay', 'mudecay'],
0044 'hadron_charge': ['hplus', 'hminus'],
0045 'coherence': ['coherent'],
0046 'model': ['bsat', 'hiAcc', 'hiDiv'],
0047 'polarization': ['unpolarised'],
0048 },
0049 },
0050 'e': {
0051 'required': ['generator', 'generator_version'],
0052 'optional': [
0053 'signal_freq', 'signal_status',
0054 'bg_tag_prefix', 'bg_files',
0055 'notes',
0056 ],
0057 'label': 'EvGen',
0058 'prefix': 'e',
0059 'model': 'EvgenTag',
0060 'choices': {
0061 'generator': [
0062 'pythia8', 'EpIC', 'BeAGLE', 'eSTARlight', 'sartre',
0063 'DEMPgen', 'lAger', 'rapgap', 'particle_gun',
0064 ],
0065 'generator_version': [
0066 '8.310', '8.306-1.0', '8.306-1.1',
0067 'v1.1.6-1.2', '1.1.6-1.0',
0068 '1.03.02-1.2', '1.03.02-2.0', '1.03.02-1.1',
0069 '1.3.0-1.0', '1.39-1.1', '1.2.4',
0070 '3.6.1-1.0', '3.310-1.0',
0071 ],
0072 'bg_tag_prefix': [
0073 'Bkg_Exact1S_2us/GoldCt/5um',
0074 'Bkg_Exact1S_2us/GoldCt/10um',
0075 ],
0076 },
0077 },
0078 's': {
0079 'required': ['detector_sim', 'sim_version'],
0080 'optional': ['background_config', 'digitization', 'notes'],
0081 'label': 'Simulation',
0082 'prefix': 's',
0083 'model': 'SimuTag',
0084 'choices': {
0085 'detector_sim': ['npsim'],
0086 'sim_version': ['26.02.0'],
0087 'background_config': [
0088 'none',
0089 'Bkg_Exact1S_2us/GoldCt/5um',
0090 'Bkg_Exact1S_2us/GoldCt/10um',
0091 ],
0092 },
0093 },
0094 'r': {
0095 'required': ['reco_version', 'reco_config'],
0096 'optional': ['calibration_tag', 'alignment_tag', 'notes'],
0097 'label': 'Reconstruction',
0098 'prefix': 'r',
0099 'model': 'RecoTag',
0100 'choices': {
0101 'reco_version': ['26.02.0'],
0102 'reco_config': ['standard'],
0103 },
0104 },
0105 }
0106
0107
0108 def get_tag_model(tag_type):
0109 from . import models
0110 return getattr(models, TAG_SCHEMAS[tag_type]['model'])
0111
0112
0113 def _schema_to_param_defs(tag_type):
0114 schema = TAG_SCHEMAS[tag_type]
0115 choices = schema.get('choices', {})
0116 defs = []
0117 for i, name in enumerate(schema['required']):
0118 defs.append({
0119 'name': name,
0120 'type': 'string',
0121 'required': True,
0122 'choices': choices.get(name, []),
0123 'allow_other': True,
0124 'sort_order': i,
0125 })
0126 offset = len(schema['required'])
0127 for i, name in enumerate(schema['optional']):
0128 defs.append({
0129 'name': name,
0130 'type': 'string',
0131 'required': False,
0132 'choices': choices.get(name, []),
0133 'allow_other': True,
0134 'sort_order': offset + i,
0135 })
0136 return defs
0137
0138
0139 def _state_key(tag_type):
0140 return f'pcs_param_defs_{tag_type}'
0141
0142
0143 def get_param_defs(tag_type):
0144 from monitor_app.models import PersistentState
0145 key = _state_key(tag_type)
0146 try:
0147 ps = PersistentState.objects.get(id=1)
0148 defs = ps.state_data.get(key)
0149 if defs is not None:
0150 return defs
0151 except PersistentState.DoesNotExist:
0152 pass
0153 return seed_param_defs(tag_type)
0154
0155
0156 def seed_param_defs(tag_type):
0157 from monitor_app.models import PersistentState
0158 defs = _schema_to_param_defs(tag_type)
0159 ps, _ = PersistentState.objects.get_or_create(id=1, defaults={'state_data': {}})
0160 ps.state_data[_state_key(tag_type)] = defs
0161 ps.save(update_fields=['state_data', 'updated_at'])
0162 return defs
0163
0164
0165 def save_param_defs(tag_type, defs):
0166 from monitor_app.models import PersistentState
0167 ps, _ = PersistentState.objects.get_or_create(id=1, defaults={'state_data': {}})
0168 ps.state_data[_state_key(tag_type)] = defs
0169 ps.save(update_fields=['state_data', 'updated_at'])
0170
0171
0172 def validate_parameters(tag_type, parameters):
0173 defs = get_param_defs(tag_type)
0174 required = [d['name'] for d in defs if d.get('required')]
0175 missing = [f for f in required if f not in parameters or not parameters[f]]
0176 if missing:
0177 return False, f"Missing required parameters: {', '.join(missing)}"
0178 return True, None