85 lines
6.9 KiB
Python
85 lines
6.9 KiB
Python
"""BR72–75 scoped catalog availability, creation and read-only assistant answers."""
|
||||
|
|
from services import assistant_store as store, component_registry as registry
|
|||
|
|
CARD_KINDS = ('data_card', 'skill', 'rule_set', 'template')
|
|||
|
|
|
|||
|
|
def hidden(db, component_id):
|
|||
|
|
try: return store.get(db, 'component_availability', component_id)['hidden']
|
|||
|
|
except KeyError: return False
|
|||
|
|
|
|||
|
|
def available_plan(db, plan):
|
|||
|
|
refs=[plan.get('component_id') or plan['id'], plan.get('template')]+plan.get('skills',[])+plan.get('rules',[])+plan.get('cards',[])
|
|||
|
|
return not any(hidden(db,key) for key in refs if key)
|
|||
|
|
|
|||
|
|
def set_visibility(db, card, actor, value):
|
|||
|
|
component_id=card.get('component_id') or card['id']
|
|||
|
|
if card.get('status')!='published': raise ValueError('仅已发布卡片可设置隐藏;草稿需先评测发布')
|
|||
|
|
result=store.put(db,'component_availability',{'id':component_id,'hidden':value,'updated_by':actor,'updated_at':store.now()})
|
|||
|
|
store.event(db,card.get('project_id',''),'component.visibility',actor,{'component_id':component_id,'hidden':value})
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
def catalog(db, project_id, domain, admin=False):
|
|||
|
|
rows=[registry.resolve(db,p['id']) for p in store.listing(db,'release')]
|
|||
|
|
if admin:
|
|||
|
|
published={(row['id'],row['version']) for row in rows}
|
|||
|
|
rows += [row for row in store.listing(db,'component_draft',project_id) if (row.get('component_id'),row['version']) not in published]
|
|||
|
|
result=[]
|
|||
|
|
for row in rows:
|
|||
|
|
if row.get('kind') not in CARD_KINDS or row.get('scope')!=domain or row.get('project_id') not in (None,'',project_id):continue
|
|||
|
|
if row.get('id')=='review.analysis' or row.get('audience')==['reviewer']:continue
|
|||
|
|
row=dict(row,hidden=hidden(db,row.get('component_id') or row['id']))
|
|||
|
|
if admin or not row['hidden']:result.append(row)
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
def create(db, context, actor, kind, name, content):
|
|||
|
|
if kind not in ('data_card','template'):raise ValueError('该入口仅支持数据卡与模板卡')
|
|||
|
|
allowed={'data_card':{'source_id','mapping','sql','tables','alignment','parameter','field_units','unit'},'template':{'sections','instructions'}}[kind]
|
|||
|
|
if set(content)-allowed:raise ValueError('配置包含未支持的字段')
|
|||
|
|
if kind=='template':
|
|||
|
|
sections=content.get('sections')
|
|||
|
|
if not isinstance(sections,list) or not 1<=len(sections)<=12:raise ValueError('模板需提供1至12个章节')
|
|||
|
|
if any(not isinstance(item,dict) or set(item)-{'id','title','dependencies'} or not isinstance(item.get('id'),str) or not isinstance(item.get('title'),str) or not item['id'] or not item['title'] or len(item['title'])>120 or not isinstance(item.get('dependencies',[]),list) for item in sections):raise ValueError('模板章节格式不合法')
|
|||
|
|
else:
|
|||
|
|
from services.assistant_materials import FIELDS
|
|||
|
|
mapping=content.get('mapping')
|
|||
|
|
if not isinstance(mapping,dict) or not mapping or set(mapping)-set(FIELDS.values()) or any(not isinstance(value,str) or not value or len(value)>120 for value in mapping.values()):raise ValueError('数据卡字段映射需使用标准字段')
|
|||
|
|
if not isinstance(content.get('sql'),str):raise ValueError('数据卡需要只读SQL')
|
|||
|
|
body={**content,'id':store.uid('manual.'+kind),'kind':kind,'name':name,'version':'1.0.0','scope':context['data_domain'],'project_id':context['id'],'origin':'manual','edited_by':actor}
|
|||
|
|
if kind=='data_card':
|
|||
|
|
source=registry.resolve(db,body.get('source_id',''))
|
|||
|
|
if source.get('scope')!=context['data_domain'] or source.get('project_id') not in (None,'',context['id']):raise ValueError('数据源范围不匹配')
|
|||
|
|
from api.risk_agent_connectors import validate_query
|
|||
|
|
dialect={'PostgreSQL':'postgres','MySQL':'mysql'}.get(source['type'],'sqlite')
|
|||
|
|
validate_query(body.get('sql',''),source.get('allowed_tables',[]),dialect)
|
|||
|
|
return registry.draft(db,body,actor)
|
|||
|
|
|
|||
|
|
def answer(db, project_id, domain, role, message):
|
|||
|
|
rows=catalog(db,project_id,domain,role=='admin')
|
|||
|
|
text=message.lower();kind=next((k for k,words in [('skill',('skill','技能')),('rule_set',('rule','规则')),('template',('模板',)),('data_card',('数据卡',))] if any(w in text for w in words)),None)
|
|||
|
|
if kind:rows=[r for r in rows if r['kind']==kind]
|
|||
|
|
if '金融' in text or '租赁' in text:
|
|||
|
|
rows=[r for r in rows if any(w in str(r) for w in ('financial.','lease.','corporate.','租赁','现金流','财务','金租'))]
|
|||
|
|
names=[r['name']+'('+('隐藏' if r['hidden'] else '已发布' if r.get('status')=='published' else '草稿')+')' for r in rows]
|
|||
|
|
return {'intent':'catalog_query','message':f'当前项目可访问的'+(kind or '卡片')+f'共 {len(rows)} 张。'+('\n'+'\n'.join(names) if names else ' 当前没有匹配卡片。'),'items':[{'id':r['id'],'name':r['name'],'kind':r['kind'],'status':r['status'],'hidden':r['hidden']} for r in rows],'url':f'/configuration-center?project={project_id}' if role=='admin' else f'/prescreen-workbench?project={project_id}'}
|
|||
|
|
|
|||
|
|
def seed_fact_source(db):
|
|||
|
|
source_id='source.project-facts'
|
|||
|
|
try:registry.resolve(db,source_id);seed_fact_card(db);return
|
|||
|
|
except KeyError:pass
|
|||
|
|
body=registry.normalize({'id':source_id,'kind':'data_source','name':'当前项目已确认事实(只读)','type':'demo','scope':'demo','status':'published','version':'1.0.0','allowed_tables':['project','finance','credit','assets']},'system-demo')
|
|||
|
|
registry.validate(body);version_id=source_id+'@1.0.0'
|
|||
|
|
store.put(db,'component_version',dict(body,id=version_id,component_id=source_id),immutable=True)
|
|||
|
|
store.put(db,'release',{'id':source_id,'version_id':version_id,'scope':'demo'})
|
|||
|
|
store.event(db,'','source.demo_registered','system-demo',{'source_id':source_id,'purpose':'controlled-project-facts'})
|
|||
|
|
seed_fact_card(db)
|
|||
|
|
|
|||
|
|
def seed_fact_card(db):
|
|||
|
|
card_id='data.initial-finance'
|
|||
|
|
try:registry.resolve(db,card_id);return
|
|||
|
|
except KeyError:pass
|
|||
|
|
fields=('financial.total_assets','financial.total_liabilities','financial.operating_cashflow')
|
|||
|
|
body=registry.normalize({'id':card_id,'kind':'data_card','name':'初步财务与融资事实','description':'只读取当前项目已确认事实,保留字段口径与项目范围。','status':'published','scope':'demo','version':'1.0.0','source_id':'source.project-facts','sql':'SELECT * FROM finance WHERE project_id=:project_id','tables':['finance'],'parameter':'project_id','mapping':{'financial.total_assets':'assets','financial.total_liabilities':'liabilities','financial.operating_cashflow':'cashflow'},'alignment':{'project_field':'project_id','selection':'single'},'field_units':{field:'万元' for field in fields}},'system-demo')
|
|||
|
|
registry.validate(body);version_id=card_id+'@1.0.0'
|
|||
|
|
store.put(db,'component_version',dict(body,id=version_id,component_id=card_id),immutable=True)
|
|||
|
|
store.put(db,'release',{'id':card_id,'version_id':version_id,'scope':'demo'})
|
|||
|
|
store.event(db,'','card.demo_registered','system-demo',{'card_id':card_id,'purpose':'confirmed-project-facts'})
|