Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-27 07:41:42

0001 """
0002 PCS (Physics Configuration System) MCP tools — tag browsing and lookup.
0003 
0004 Each tool registers with the MCP server and queries Django ORM via sync_to_async.
0005 """
0006 
0007 from asgiref.sync import sync_to_async
0008 from mcp_server import mcp_server as mcp
0009 
0010 
0011 def _list_tags_sync(tag_type, category=None, status=None, creator=None,
0012                     search=None):
0013     """List tags with filtering. Returns list of tag summaries."""
0014     from pcs.schemas import TAG_SCHEMAS, get_tag_model
0015 
0016     if tag_type not in TAG_SCHEMAS:
0017         return {"error": f"Invalid tag_type '{tag_type}'. Use: p, e, s, r"}
0018 
0019     model = get_tag_model(tag_type)
0020     qs = model.objects.order_by('-tag_number')
0021 
0022     if tag_type == 'p':
0023         qs = qs.select_related('category')
0024         if category:
0025             qs = qs.filter(category__name__iexact=category)
0026 
0027     if status:
0028         qs = qs.filter(status=status.lower())
0029     if creator:
0030         qs = qs.filter(created_by=creator)
0031 
0032     if search:
0033         from django.db.models import Q
0034         q = Q(description__icontains=search) | Q(tag_label__icontains=search)
0035         qs = qs.filter(q)
0036 
0037     tags = []
0038     for t in qs:
0039         entry = {
0040             'tag_label': t.tag_label,
0041             'status': t.status,
0042             'description': t.description,
0043             'created_by': t.created_by,
0044             'parameters': t.parameters,
0045         }
0046         if tag_type == 'p':
0047             entry['category'] = t.category.name
0048         tags.append(entry)
0049 
0050     schema = TAG_SCHEMAS[tag_type]
0051     return {
0052         'tag_type': tag_type,
0053         'label': schema['label'],
0054         'count': len(tags),
0055         'tags': tags,
0056     }
0057 
0058 
0059 def _get_tag_sync(tag_label):
0060     """Get a single tag by label (e.g. 'p1001', 'e3', 'r1')."""
0061     label = tag_label.strip().lower()
0062     if not label or label[0] not in ('p', 'e', 's', 'r'):
0063         return {"error": f"Invalid tag label '{tag_label}'. Format: p1001, e3, s1, r1"}
0064 
0065     prefix = label[0]
0066     try:
0067         number = int(label[1:])
0068     except ValueError:
0069         return {"error": f"Invalid tag number in '{tag_label}'"}
0070 
0071     from pcs.schemas import get_tag_model
0072     model = get_tag_model(prefix)
0073 
0074     try:
0075         t = model.objects.get(tag_number=number)
0076     except model.DoesNotExist:
0077         return {"error": f"Tag {tag_label} not found"}
0078 
0079     result = {
0080         'tag_label': t.tag_label,
0081         'tag_number': t.tag_number,
0082         'status': t.status,
0083         'description': t.description,
0084         'parameters': t.parameters,
0085         'created_by': t.created_by,
0086         'created_at': t.created_at.isoformat(),
0087     }
0088     if prefix == 'p':
0089         t_with_cat = model.objects.select_related('category').get(tag_number=number)
0090         result['category'] = t_with_cat.category.name
0091         result['category_digit'] = t_with_cat.category.digit
0092 
0093     return result
0094 
0095 
0096 def _search_tags_sync(query, tag_type=None):
0097     """Search across tag descriptions and parameters."""
0098     from pcs.schemas import TAG_SCHEMAS, get_tag_model
0099 
0100     types = [tag_type] if tag_type else ['p', 'e', 's', 'r']
0101     results = []
0102 
0103     for tt in types:
0104         if tt not in TAG_SCHEMAS:
0105             continue
0106         model = get_tag_model(tt)
0107         qs = model.objects.order_by('-tag_number')
0108         if tt == 'p':
0109             qs = qs.select_related('category')
0110 
0111         q_lower = query.lower()
0112         for t in qs:
0113             searchable = ' '.join([
0114                 t.tag_label, t.description,
0115                 ' '.join(str(v) for v in t.parameters.values()),
0116             ]).lower()
0117             if q_lower in searchable:
0118                 entry = {
0119                     'tag_label': t.tag_label,
0120                     'status': t.status,
0121                     'description': t.description,
0122                     'parameters': t.parameters,
0123                 }
0124                 if tt == 'p':
0125                     entry['category'] = t.category.name
0126                 results.append(entry)
0127 
0128     return {
0129         'query': query,
0130         'count': len(results),
0131         'tags': results,
0132     }
0133 
0134 
0135 @mcp.tool()
0136 async def pcs_list_tags(
0137     tag_type: str,
0138     category: str = None,
0139     status: str = None,
0140     creator: str = None,
0141     search: str = None,
0142 ) -> dict:
0143     """
0144     List PCS tags (production task configurations) with optional filtering.
0145 
0146     PCS tags capture physics process, event generation, simulation, and
0147     reconstruction configurations for ePIC Monte Carlo production campaigns.
0148 
0149     Args:
0150         tag_type: Tag type — 'p' (physics), 'e' (evgen), 's' (simu), 'r' (reco). Required.
0151         category: Physics tags only — filter by category name (e.g. 'DIS', 'DVCS', 'EXCLUSIVE').
0152         status: Filter by status: 'draft' or 'locked'.
0153         creator: Filter by creator username.
0154         search: Text search in tag label and description.
0155 
0156     Returns:
0157         tag_type, label, count, and list of tags with: tag_label, status,
0158         description, parameters, created_by, category (physics only).
0159     """
0160     return await sync_to_async(_list_tags_sync)(
0161         tag_type=tag_type, category=category, status=status,
0162         creator=creator, search=search,
0163     )
0164 
0165 
0166 @mcp.tool()
0167 async def pcs_get_tag(tag_label: str) -> dict:
0168     """
0169     Get full details of a single PCS tag by its label.
0170 
0171     Args:
0172         tag_label: The tag label, e.g. 'p1001', 'e3', 's1', 'r1'.
0173                    Case-insensitive.
0174 
0175     Returns:
0176         tag_label, tag_number, status, description, parameters (all key-value
0177         pairs), created_by, created_at, and category/category_digit for physics tags.
0178     """
0179     return await sync_to_async(_get_tag_sync)(tag_label=tag_label)
0180 
0181 
0182 @mcp.tool()
0183 async def pcs_search_tags(
0184     query: str,
0185     tag_type: str = None,
0186 ) -> dict:
0187     """
0188     Search across PCS tags by text in label, description, or parameter values.
0189 
0190     Use this when you don't know the exact tag label but know a keyword like
0191     'photoproduction', 'pythia8', 'eAu', or 'minQ2=1000'.
0192 
0193     Args:
0194         query: Search text (case-insensitive). Matches against tag label,
0195                description, and all parameter values.
0196         tag_type: Optional — restrict to one type: 'p', 'e', 's', 'r'.
0197                   If omitted, searches all tag types.
0198 
0199     Returns:
0200         query, count, and list of matching tags with: tag_label, status,
0201         description, parameters, category (physics only).
0202     """
0203     return await sync_to_async(_search_tags_sync)(
0204         query=query, tag_type=tag_type,
0205     )