搜索​​​​

清除过滤器
文章
Lilian Huang · 四月 16

Iris-AgenticAI: 基于OpenAI智能体SDK驱动的企业自动化多智能体工作流平台

Hi 大家好在本文中,我讲介绍我的应用 iris-AgenticAI . 代理式人工智能的兴起标志着人工智能与世界互动方式的变革性飞跃--从静态响应转变为动态、目标驱动的问题解决方式。参看 OpenAI’s Agentic SDK , OpenAI Agents SDK使您能够在一个轻量级、易用且抽象程度极低的软件包中构建代理人工智能应用程序。它是我们之前的代理实验 Swarm 的生产就绪升级版。 该应用展示了下一代自主人工智能系统,这些系统能够进行推理、协作,并以类似人类的适应能力执行复杂任务。 应用功能 Agent Loop 🔄 一个内置循环,可自主管理工具的执行,将结果发回 LLM,并迭代直至任务完成。 Python-First 🐍 利用本地 Python 语法(装饰器、生成器等)来协调和连锁代理,而无需外部 DSL。 Handoffs 🤝 通过在专业代理之间委派任务,无缝协调多代理工作流程。 Function Tools ⚒️ 用 @tool 修饰任何 Python 函数,可立即将其集成到代理的工具包中。 Vector Search (RAG) 🧠 原生集成向量存储(IRIS),用于 RAG 检索。 Tracing 🔍 内置跟踪功能,可实时可视化、调试和监控代理工作流(想想 LangSmith 的替代方案)。 MCP Servers 🌐 通过 stdio 和 HTTP 支持模型上下文协议(MCP),实现跨进程代理通信。 Chainlit UI 🖥️ 集成 Chainlit 框架,可使用最少的代码构建交互式聊天界面。 Stateful Memory 🧠 跨会话保存聊天历史、上下文和代理状态,以实现连续性和长期任务。 代理 代理是应用程序的核心构件。代理是一个大型语言模型(LLM),配置有指令和工具。基本配置您需要配置的代理最常见的属性,包括: Instructions (说明):也称为开发人员信息或系统提示。model:要使用的 LLM,以及可选的 model_settings,用于配置温度、top_p 等模型调整参数。tools工具: 代理用来完成任务的工具。 from agents import Agent, ModelSettings, function_tool @function_tool def get_weather(city: str) -> str: return f"The weather in {city} is sunny" agent = Agent( name="Haiku agent", instructions="Always respond in haiku form", model="o3-mini", tools=[get_weather], ) 运行代理 您可以通过 Runner 类运行代理。您有 3 个选项: 1.Runner.run():异步运行并返回 RunResult。2.Runner.run_sync(),这是一种同步方法,只是在引擎盖下运行 .run()。3.Runner.run_streamed():异步运行并返回 RunResultStreaming。它以流式模式调用 LLM,并在接收到事件时将其流式传输给您。 from agents import Agent, Runner async def main(): agent = Agent(name="Assistant", instructions="You are a helpful assistant") result = await Runner.run(agent, "Write a haiku about recursion in programming.") print(result.final_output) # Code within the code, # Functions calling themselves, # Infinite loop's dance. 代理架构 该应用程序由 7 个专业代理组成: 1. 分诊代理 🤖 角色: 主要路由器,接收用户输入,并通过切换分配任务 示例: 路由 “显示生产错误” → IRIS 生产代理 2.矢量搜索代理 🤖 作用: 提供 IRIS 2025.1 版本说明详情(RAG 功能) 示例: 路由 “向我提供发行说明摘要”→矢量搜索代理 3. IRIS 仪表板代理 🤖功能: 提供实时管理门户指标:明文副本。 ApplicationErrors, CSPSessions, CacheEfficiency, DatabaseSpace, DiskReads, DiskWrites, ECPAppServer, ECPDataServer, GloRefs, JournalStatus, LicenseCurrent, LockTable, Processes, SystemUpTime, WriteDaemon, [...] 4. IRIS Running Process Agent 🤖 功能: 监控活动进程的详细信息: Process ID | Namespace | Routine | State | PidExternal 5. IRIS Production Agent 🤖 角色: 提供生产详情以及启动和停止生产的功能。 6. WebSearch Agent 🤖 功能: 通过 API 集成执行上下文网络搜索 7.Order Agent 🤖 功能: 使用订单 ID 检索订单状态 交接 交接允许代理将任务委托给另一个代理。这在不同代理擅长不同领域的情况下尤其有用。例如,客户支持应用程序可能会有专门处理订单状态、退款、常见问题等任务的代理。 分流代理是我们的主代理,它会根据用户输入将任务分配给另一个代理 #TRIAGE AGENT, Main agent receives user input and delegates to other agent by using handoffs triage_agent = Agent( name="Triage agent", instructions=( "Handoff to appropriate agent based on user query." "if they ask about Release Notes, handoff to the vector_search_agent." "If they ask about production, handoff to the production agent." "If they ask about dashboard, handoff to the dashboard agent." "If they ask about process, handoff to the processes agent." "use the WebSearchAgent tool to find information related to the user's query and do not use this agent is query is about Release Notes." "If they ask about order, handoff to the order_agent." ), handoffs=[vector_search_agent,production_agent,dashboard_agent,processes_agent,order_agent,web_search_agent] ) 跟踪 Agents SDK 包括内置跟踪功能,可收集代理运行期间事件的全面记录: LLM 生成、工具调用、切换、防护栏,甚至发生的自定义事件。使用跟踪仪表板,您可以在开发和生产过程中调试、可视化和监控工作流。https://platform.openai.com/logs 应用界面 应用工作流程矢量搜索代理 矢量搜索代理自动获取 New in InterSystems IRIS 2025.1 如果数据还不存在,只需将文本信息输入 IRIS 矢量存储区一次。 使用下面的查询来获取数据 SELECT id, embedding, document, metadata FROM SQLUser.AgenticAIRAG 分流代理接收用户输入,将问题转给矢量搜索代理。 IRIS 仪表盘代理 分流代理接收用户输入,将问题路由到 IRIS 仪表板代理。 IRIS 流程代理分流代理接收用户输入,将问题路由到 IRIS 流程代理。IRIS 生产代理 使用生产代理启动和停止生产。使用生产代理获取生产详情。 本地代理 分流代理接收用户输入,将问题转给本地订单代理。WebSearch 代理 在这里,分流代理接收到两个问题,并将两个问题都路由到 WebSearcg 代理。 MCP Server 应用 MCP Server在这里运行 https://localhost:8000/sse 下面是启动 MCP 服务器的代码: import os import shutil import subprocess import time from typing import Any from dotenv import load_dotenv load_dotenv() #Get OPENAI Key, if not fond in .env then get the GEIMINI API KEY #IF Both defined then take OPENAI Key openai_api_key = os.getenv("OPENAI_API_KEY") if not openai_api_key: raise ValueError("OPENAI_API_KEY is not set. Please ensure to defined in .env file.") if __name__ == "__main__": # Let's make sure the user has uv installed if not shutil.which("uv"): raise RuntimeError( "uv is not installed. Please install it: https://docs.astral.sh/uv/getting-started/installation/" ) # We'll run the SSE server in a subprocess. Usually this would be a remote server, but for this # demo, we'll run it locally at http://localhost:8000/sse process: subprocess.Popen[Any] | None = None try: this_dir = os.path.dirname(os.path.abspath(__file__)) server_file = os.path.join(this_dir, "MCPserver.py") print("Starting SSE server at http://localhost:8000/sse ...") # Run `uv run server.py` to start the SSE server process = subprocess.Popen(["uv", "run", server_file]) # Give it 3 seconds to start time.sleep(3) print("SSE server started. Running example...\n\n") except Exception as e: print(f"Error starting SSE server: {e}") exit(1) MCP 服务器配备了以下工具: 提供 IRIS 2025.1 发行说明详情(矢量搜索) IRIS 信息工具 检查天气工具 查找暗语工具(本地功能) 加法工具(本地功能) MCP 应用在这里运行 http://localhost:8001 MCP 服务器矢量搜索(RAG)功能 MCP 服务器配备 InterSystems IRIS 向量搜索摄取功能和检索增强生成 (RAG) 功能。 MCP Server other functionality The MCP Server dynamically delegates tasks to the appropriate tool based on user input. 更多详情,请访问 iris-AgenticAI open exchange 界面。 谢谢!
文章
Jingwei Wang · 一月 19, 2023

介绍使用嵌入式 Python 进行 Web 抓取

什么是网页抓取: 简单来说,网络抓取、网络收获或网络数据提取是从网站收集大数据(非结构化)的自动化过程。用户可以根据需要提取特定站点上的所有数据或特定数据。收集的数据可以以结构化格式存储以供进一步分析。 网页抓取涉及的步骤: 找到您要抓取的网页的 URL 通过检查选择特定元素 编写代码获取被选元素的内容 以需要的格式存储数据 就这么简单! 用于网络抓取的流行库/工具是: Selenium – 用于测试 Web 应用程序的框架 BeautifulSoup – 用于从 HTML、XML 和其他标记语言中获取数据的 Python 库 Pandas – 用于数据操作和分析的 Python 库 什么是Beautiful Soup? Beautiful Soup 是一个纯 Python 库,用于从网站中提取结构化数据。它允许您解析来自 HTML 和 XML 文件的数据。它充当辅助模块,并以与使用其他可用开发人员工具以网页交互的方式与 HTML 交互。 它通常可以为程序员节省数小时或数天的工作时间,因为它可以与您最喜欢的解析器(如 lxml 和 html5lib)一起使用,以提供 Python organic方式来导航、搜索、并修改解析树。 beautiful soup 的另一个强大而有用的功能是它可以智能地将正在获取的文档转换为 Unicode 并将传出文档转换为 UTF-8。作为一名开发人员,您不必关心这一点,除非文档内在未指定编码或 Beautiful Soup 无法检测到编码。 与其他一般解析或抓取技术相比,它也被认为更快。 在今天的文章中,我们将使用带有对象脚本的嵌入式 Python 来抓取 ae.indeed.com 上的 Python 职位空缺和公司 第 1 步 -找到您要抓取的网页的 URL。 url = https://ae.indeed.com/jobs?q=python&l=Dubai&start=0 我们要从中抓取数据的网页看起来像这样 为了简单和学习目的,我们将提取“职位”和“公司”,输出将类似于下面的屏幕截图。 我们将使用两个 python 库。 requests :Requests 是 Python 编程语言的 HTTP 库。该项目的目标是使 HTTP 请求更简单、更人性化。 bs4 for BeautifulSoup :BeautifulSoup 是一个用于解析 HTML 和 XML 文档的 Python 包。它为已解析的页面创建一个解析树,可用于从 HTML 中提取数据,这对于网络抓取很有用。 让我们安装这个 python 包 (windows) irispip install --target C:\InterSystems\IRISHealth\mgr\python bs4 irispip install --target C:\InterSystems\IRISHealth\mgr\python requests 让我们将 python 库导入到 ObjectScript Class PythonTesting.WebScraper Extends %Persistent { // pUrl = https://ae.indeed.com/jobs?q=python&l=Dubai&start= // pPage = 0 ClassMethod ScrapeWebPage(pUrl, pPage) { // imports the requests python library set requests = ##class ( %SYS.Python ).Import( "requests" ) // import the bs4 python library set soup = ##class ( %SYS.Python ).Import( "bs4" ) // import builtins package which contains all of the built-in identifiers set builtins = ##class ( %SYS.Python ).Import( "builtins" ) } 让我们使用请求收集 html 数据;注意:我们从谷歌搜索“my user agent”中获取的用户代理url为“https://ae.indeed.com/jobs?q=python&l=Dubai&start=”,pPage为页码 我们将使用 requests 对 URL 执行 http get 请求并将响应存储在“req” set headers = { "User-Agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36" } set url = "https://ae.indeed.com/jobs?q=python&l=Dubai&start=" _pPage set req = requests.get(url, "headers=" _headers) req 对象将具有从网页返回的 html。 让我们通过 BeautifulSoup html 解析器运行它,以便我们可以提取作业数据。 set soupData = soup.BeautifulSoup(req.content, "html.parser" ) set title = soupData.title.text W !,title 标题看起来如下 第二步:通过检查选择需要的元素。 在这种情况下,我们对通常位于 <div> 标记中的作业列表感兴趣,在您的浏览器中,您可以检查该元素以找到 div 类。在我们的例子中,所需的信息存储在 <div class="cardOutline tapItem ... </div> 第三步:编写获取选中元素内容的代码 我们将使用 BeautifulSoup 上的 find_all 功能来查找所有包含类名“cardOutline”的 <div> 标签 //parameters to python would be sent as a python dictionary set divClass = { "class" : "cardOutline" } set divsArr = soupData. "find_all" ( "div" ,divClass...) 这将返回一个列表,我们可以遍历该列表并提取职位和公司 第 4 步:以所需格式存储/显示数据。 在下面的示例中,我们将数据写入终端。 set len = builtins.len(divsArr) W !, "Job Title" , $C ( 9 )_ " --- " _ $C ( 9 ), "Company" for i = 1 : 1 :len { Set item = divsArr. "__getitem__" (i - 1 ) set title = $ZSTRIP (item.find( "a" ).text, "<>W" ) set companyClass = { "class_" : "companyName" } set company = $ZSTRIP (item.find( "span" , companyClass...).text, "<>W" ) W !,title, $C ( 9 ), " --- " , $C ( 9 ),company } 请注意,我们正在使用 builtins.len() 来获取 divsArr 列表的长度 标识符名称:命名标识符的规则在 ObjectScript 和 Python 之间是不同的。例如,Python 方法名中允许使用下划线 (_),实际上广泛用于所谓的“dunder”方法和属性(“dunder”是“双下划线”的缩写),例如 __getitem__ 或 __class__ .要从 ObjectScript 使用此类标识符,请将它们括在双引号中:关于标识符名称的系统间文档 类方法示例 类方法 ScrapeWebPage(pUrl, pPage) // pUrl = https://ae.indeed.com/jobs?q=python&l=Dubai&start= // pPage = 0 ClassMethod ScrapeWebPage(pUrl, pPage) { set requests = ##class ( %SYS.Python ).Import( "requests" ) set soup = ##class ( %SYS.Python ).Import( "bs4" ) set builtins = ##class ( %SYS.Python ).Builtins() set headers = { "User-Agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36" } set url = pUrl_pPage set req = requests.get(url, "headers=" _headers) set soupData = soup.BeautifulSoup(req.content, "html.parser" ) set title = soupData.title.text W !,title set divClass = { "class_" : "cardOutline" } set divsArr = soupData. "find_all" ( "div" ,divClass...) set len = builtins.len(divsArr) W !, "Job Title" , $C ( 9 )_ " --- " _ $C ( 9 ), "Company" for i = 1 : 1 :len { Set item = divsArr. "__getitem__" (i - 1 ) set title = $ZSTRIP (item.find( "a" ).text, "<>W" ) set companyClass = { "class_" : "companyName" } set company = $ZSTRIP (item.find( "span" , companyClass...).text, "<>W" ) W !,title, $C ( 9 ), " --- " , $C ( 9 ),company } } 下一步.. 使用对象脚本和嵌入式 python 以及几行代码;我们可以很容易地从我们最喜欢的工作网站上抓取数据,收集工作名称、公司、薪水、工作描述和电子邮件/链接。例如,如果您有多个页面,您可以使用页面轻松遍历它们可以将此数据添加到 pandas 数据框并删除重复项,可以根据您感兴趣的特定关键字应用过滤器。通过 numpy 运行此数据,并获得一些线图或者对数据执行 One-Hot 编码,并创建/训练您的 ML 模型,如果有您感兴趣的特定职位空缺,请向自己发送通知。 😉快乐编码! 别忘了按赞按钮😃
文章
Michael Lei · 九月 18, 2023

开发者作品展示--几乎实现的向量支持

如今,关于大语言模型、人工智能等的消息不绝于耳。向量数据库是其中的一部分,并且已经有非IRIS的技术实现了向量数据库。 为什么是向量? 相似性搜索:向量可以进行高效的相似性搜索,例如在数据集中查找最相似的项目或文档。传统的关系数据库是为精确匹配搜索而设计的,不适合图像或文本相似性搜索等任务。 灵活性:向量表示形式用途广泛,可以从各种数据类型派生,例如文本(通过 Word2Vec、BERT 等嵌入)、图像(通过深度学习模型)等。 跨模态搜索:向量可以跨不同数据模态进行搜索。例如,给定图像的向量表示,人们可以在多模式数据库中搜索相似的图像或相关文本。 还有许多其他原因。 因此,对于这次 pyhon 竞赛,我决定尝试实现这种支持。不幸的是我没能及时完成它,下面我将解释原因。 有几件重要的事情必须完成,才能使其充实 使用 SQL 接受并存储向量化数据,简单的示例(本例中的 3 是维度数量,每个字段都是固定的,并且该字段中的所有向量都必须具有精确的维度) create table items(embedding vector( 3 )); insert into items (embedding) values ( '[1,2,3]' ); insert into items (embedding) values ( '[4,5,6]' ); 相似度函数,相似度有不同的算法,适合对少量数据进行简单搜索,不使用索引 -- Euclidean distance select embedding, vector.l2_distance(embedding, '[9,8,7]' ) distance from items order by distance; -- Cosine similarity select embedding, vector.cosine_distance(embedding, '[9,8,7]' ) distance from items order by distance; -- Inner product select embedding, -vector.inner_product(embedding, '[9,8,7]' ) distance from items order by distance; 自定义索引,有助于更快地搜索大量数据,索引可以使用不同的算法,并使用与上面不同的距离函数,以及其他一些选项 新南威尔士州 倒排文件索引 搜索将使用创建的索引,其算法将找到所请求的信息。 插入向量 该向量应该是一个数值数组,可以是整数或浮点数,也可以是有符号的或无符号的。在IRIS中我们可以将其存储为$listbuild,它具有良好的表示性,已经支持,只需要实现从ODBC到逻辑的转换。 然后,可以使用外部驱动程序(例如 ODBC/JDBC)或使用 ObjectScript 从 IRIS 内部以纯文本形式插入值 普通 SQL insert into items (embedding) values ( '[1,2,3]' ); 来自ObjectScript set rs = ##class ( %SQL.Statement ). %ExecDirect (, "insert into test.items (embedding) values ('[1,2,3]')" ) set rs = ##class ( %SQL.Statement ). %ExecDirect (, "insert into test.items (embedding) values (?)" , $listbuild ( 2 , 3 , 4 )) 或者嵌入式 SQL &sql( insert into test.items (embedding ) values ('[ 1 , 2 , 3 ]')) set val = $listbuild ( 2 , 3 , 4 ) &sql( insert into test.items (embedding ) values (:val)) 它将始终存储为 $lb(),并在 ODBC 中以文本格式返回 意外行为 在使用 DBeaver 进行测试期间,我发现连接后的第一行插入正确,但所有其他行均按原样插入,没有任何验证或转换。 然后我发现,JDBC默认使用快速插入,在这种情况下,它将插入的数据直接存储到全局变量中,所以我必须手动将其关闭 在 DBeaver 中,在 FeatureOption 字段中选择 optfastSelect 计算 我们需要向量来支持两个向量之间距离的计算 为了这次比赛,我需要使用嵌入式Python,这就带来了一个问题,如何在嵌入式Python中操作$lb。 %SYS.Class中有一个方法ToList,但Python包IRIS没有内置该方法,需要通过ObjectScript方式调用它 ClassMethod l2DistancePy(v1 As dc.vector.type, v2 As dc.vector.type) As %Decimal (SCALE= 10 ) [ Language = python, SqlName = l2_distance_py, SqlProc ] { import iris import math vector_type = iris.cls('dc.vector.type') v1 = iris.cls(' %SYS.Python ').ToList(vector_type.Normalize(v1)) v2 = iris.cls(' %SYS.Python ').ToList(vector_type.Normalize(v2)) return math.sqrt(sum([(val1 - val2) ** 2 for val1, val2 in zip(v1, v2)])) } 它看起来一点也不正确。我希望 $lb 可以在 python 中即时解释为列表,或者在列表内置函数 to_list 和 from_list 中解释 另一个问题是当我尝试使用不同的方式测试此功能时。使用嵌入式Python中的SQL,使用嵌入式Python编写的SQL函数,它会崩溃。因此,我还必须添加 ObjectScript 的功能。 ModuleNotFoundError: No module named 'dc' SQL Function VECTOR.NORM_PY failed with error: SQLCODE=-400,%msg=ERROR #5002: ObjectScript error: <OBJECT DISPATCH>%0AmBm3l0tudf^%sqlcq.USER.cls37.1 *python object not found 目前在 Python 和 ObjectScript 中实现了计算距离的函数 欧氏距离 [SQL]_system@localhost:USER> select embedding, vector.l2_distance_py(embedding, '[9,8,7]' ) distance from items order by distance; + -----------+----------------------+ | embedding | distance | + -----------+----------------------+ | [4,5,6] | 5.91607978309961613 | | [1,2,3] | 10.77032961426900748 | + -----------+----------------------+ 2 rows in set Time : 0.011 s [ SQL ]_system@localhost: USER > select embedding, vector.l2_distance(embedding, '[9,8,7]' ) distance from items order by distance; + -----------+----------------------+ | embedding | distance | + -----------+----------------------+ | [4,5,6] | 5.916079783099616045 | | [1,2,3] | 10.77032961426900807 | + -----------+----------------------+ 2 rows in set Time : 0.012 s 余弦相似度 [SQL]_system@localhost:USER> select embedding, vector.cosine_distance(embedding, '[9,8,7]' ) distance from items order by distance; + -----------+---------------------+ | embedding | distance | + -----------+---------------------+ | [4,5,6] | .034536677566264152 | | [1,2,3] | .11734101007866331 | + -----------+---------------------+ 2 rows in set Time : 0.034 s [ SQL ]_system@localhost: USER > select embedding, vector.cosine_distance_py(embedding, '[9,8,7]' ) distance from items order by distance; + -----------+-----------------------+ | embedding | distance | + -----------+-----------------------+ | [4,5,6] | .03453667756626421781 | | [1,2,3] | .1173410100786632659 | + -----------+-----------------------+ 2 rows in set Time : 0.025 s 内积 [SQL]_system@localhost:USER> select embedding, vector.inner_product_py(embedding, '[9,8,7]' ) distance from items order by distance; + -----------+----------+ | embedding | distance | + -----------+----------+ | [1,2,3] | 46 | | [4,5,6] | 118 | + -----------+----------+ 2 rows in set Time : 0.035 s [ SQL ]_system@localhost: USER > select embedding, vector.inner_product(embedding, '[9,8,7]' ) distance from items order by distance; + -----------+----------+ | embedding | distance | + -----------+----------+ | [1,2,3] | 46 | | [4,5,6] | 118 | + -----------+----------+ 2 rows in set Time : 0.032 s 另外还实现了数学函数:add、sub、div、mul。 InterSystems 支持创建自己的聚合函数。因此,可以对所有向量求和或求平均值。但不幸的是,InterSystems 不支持使用相同的名称,需要使用自己的名称(和模式)来执行函数。但它不支持聚合函数的非数值结果 简单的 vector_add 函数,返回两个矢量的和 当用作聚合时,它显示 0,而预期矢量也是 建立索引 不幸的是,由于我在实现过程中遇到了一些障碍,我没能完成这一部分。 缺乏内置的 $lb 到 python 列表转换以及当 IRIS 中的矢量存储在 $lb 中时返回,并且所有具有构建索引的逻辑预计都在 Python 中,从 $lb 获取数据并将其设置回全局变量也很重要 缺乏对Global的支持 IRIS 中的 $Order,支持方向,因此可以反向使用,而Python内嵌的 order 实现没有它,因此需要读取所有键并反转它们或将末尾存储在某处 由于对上面提到的从 Python 调用的 Python 的 SQL 函数的不好的体验而产生疑问 在构建索引期间,预计会在图形中存储矢量之间的距离,但在global里保存浮点数时遇到了bug 我在工作中发现了11 个嵌入式 Python 问题,所以大部分时间都是在寻找解决方法来解决问题。在名为iris-dollar-list的 @Guillaume.Rongier7183 项目的帮助下,我成功解决了一些问题。 安装 无论如何,它仍然可用,并且可以与 IPM 一起安装,即使功能有限也可以使用 zpm "install vector" 或者在开发模式下使用 docker-compose git clone https://github.com/caretdev/iris-vector.git cd iris-vector docker-compose up -d
文章
姚 鑫 · 五月 6, 2021

第三章 使用多维存储(全局变量)(二)

# 第三章 使用多维存储(全局变量)(二) # 遍历全局变量中的数据 有许多方法可以遍历(迭代)存储在全局变量中的数据。 ## $ORDER(下一个/上一个)函数 ObjectScript `$Order`函数允许顺序访问全局中的每个节点。 `$ORDER`函数返回给定级别(下标编号)的下一个下标的值。例如,假设定义了以下全局设置: ```java Set ^Data(1) = "" Set ^Data(1,1) = "" Set ^Data(1,2) = "" Set ^Data(2) = "" Set ^Data(2,1) = "" Set ^Data(2,2) = "" Set ^Data(5,1,2) = "" ``` 要查找第一个第一级下标,我们可以使用: ```java SET key = $ORDER(^Data("")) ``` 这将返回空字符串(`“”`)之后的第一个第一级下标。(空字符串用于表示第一个条目之前的下标值;作为返回值,它用于指示没有后续的下标值。)。在本例中,`key`现在将包含值`1`。 我们可以通过在`$ORDER`表达式中使用`1`或键来查找下一个第一级下标: ```java SET key = $ORDER(^Data(key)) ``` 如果`key`的初始值为`1`,则此语句将其设置为`2`(因为`^Data(2)`是下一个第一级下标)。再次执行此语句会将`key`设置为`5`,因为这是下一个第一级下标。请注意,即使没有直接存储在`^Data(5)`中的数据,也会返回`5`。再次执行此语句将把`key`设置为空字符串(`“”`),表示没有更多的一级下标。 通过将附加下标与`$ORDER`函数一起使用,可以迭代不同的下标级别。`$order`返回其参数列表中最后一个下标的下一个值。使用上述数据,该语句如下: ```java SET key = $ORDER(^Data(1,"")) ``` 将关键字设置为`1`,因为`^Data(1,1)`是下一个二级下标。再次执行此语句会将`KEY`设置为`2`,因为这是下一个二级下标。再次执行此语句将把`key`设置为`“”`,表示在节点`^Data(1)`下没有更多的二级下标。 ## 使用$ORDER循环 下面的ObjectScript代码定义了一个简单的全局变量,然后循环遍历其所有第一级子脚本: ```java /// w ##class(PHA.TEST.Global).ReadGlobalSimpleFor() ClassMethod ReadGlobalSimpleFor() { // 清除^Data,以防它有数据 Kill ^Data // 使用示例数据填写^Data For i = 1:1:100 { // 将每个节点设置为随机人名 Set ^Data(i) = ##class(%PopulateUtils).Name() } // 在每个节点上循环 查找第一个节点 Set key = $Order(^Data("")) While (key '= "") { Write "#", key, " ", ^Data(key),! // F查找下一个节点 Set key = $Order(^Data(key)) } q "" } ``` ```java DHC-APP>w ##class(PHA.TEST.Global).ReadGlobalSimpleFor() #1 Edwards,Barbara T. #2 Ragon,Kevin K. #3 Avery,Josephine U. #4 Townsend,Buzz R. #5 Joyce,Quentin V. #6 Xenia,Ted F. #7 Chadwick,Wilma N. #8 Duquesnoy,Orson A. #9 Uberoth,Orson X. #10 Jones,Joe O. #11 Hills,Barb R. #12 Yakulis,Pat J. #13 Tesla,Al P. #14 Goncharuk,Sam J. #15 Presley,Amanda D. #16 Olsen,Kristen I. #17 Roentgen,John T. #18 Minichillo,Elmo N. #19 Koivu,Patrick R. #20 Harrison,Lawrence I. #21 Page,Agnes P. #22 Wijnschenk,Hannah L. #23 Chesire,Bart S. #24 Klingman,Liza K. #25 Smyth,Imelda J. #26 Alton,Filomena L. #27 Minichillo,Charles U. #28 Nichols,Jeff W. #29 O'Rielly,Thelma X. #30 Schaefer,Kristen G. #31 Black,Filomena R. #32 Vivaldi,Xavier B. #33 Allen,Phyllis U. #34 Mastrolito,Zelda Z. #35 Quilty,Jane V. #36 Zevon,Maureen H. #37 O'Rielly,Maureen C. #38 Olsen,Robert W. #39 Page,Milhouse D. #40 Nelson,Dick R. #41 Ironhorse,Danielle I. #42 Tweed,Rhonda T. #43 Quincy,Terry L. #44 Tsatsulin,Jocelyn C. #45 Yeats,Michelle E. #46 Jackson,Paul V. #47 Humby,Dave I. #48 Kelvin,Natasha R. #49 Kelvin,Kyra R. #50 Yoders,Agnes R. #51 Tesla,Amanda F. #52 Harrison,Christen T. #53 Allen,Nataliya J. #54 Xenia,Diane W. #55 Xenia,Phyllis E. #56 Isaksen,Pam D. #57 Waterman,Charles M. #58 Peters,Sophia N. #59 Peterson,Bart B. #60 Eastman,Edward S. #61 Young,Belinda F. #62 White,Fred G. #63 Ubertini,Lola U. #64 Uhles,Xavier T. #65 Quine,Phyllis T. #66 Hernandez,Umberto B. #67 Allen,Zelda S. #68 Harrison,David Z. #69 Harrison,Danielle T. #70 Ott,Dick D. #71 Lennon,Joe Y. #72 Quigley,Alfred M. #73 Klausner,Mario J. #74 Tsatsulin,Emily S. #75 Anderson,Edward R. #76 Lennon,Fred H. #77 DeSantis,Molly J. #78 Browne,Dave H. #79 Cunningham,Buzz L. #80 Ingersol,Edgar G. #81 Paraskiv,Linda O. #82 Beatty,Kim H. #83 Quilty,Wilma P. #84 Dunlap,Jules I. #85 Waterman,Buzz D. #86 Edison,Kim C. #87 Eagleman,Michael N. #88 Huff,Hannah K. #89 Vanzetti,Maria E. #90 Zampitello,Angela Q. #91 Anderson,Angela Z. #92 Isaacs,Charlotte Q. #93 O'Donnell,Paul A. #94 Underman,Zeke R. #95 Schultz,James I. #96 Chadbourne,Janice N. #97 Lennon,William T. #98 Vonnegut,Pam V. #99 Miller,Patricia T. #100 Hills,Charles C. ``` ## 其他$ORDER参数 ObjectScript `$ORDER`函数接受可选的第二个和第三个参数。 第二个参数是一个方向标志,指示希望在哪个方向上遍历全局变量。 默认值`1`指定正向遍历,而`-1`指定反向遍历。 第三个参数(如果存在)包含一个局部变量名。 如果`$ORDER`找到的节点包含数据,则将找到的数据写入这个本地变量。 当您在一个全局循环中,并且您对节点值和下标值感兴趣时,这样操作更有效。 ## $QUERY函数 如果需要访问全局变量中的每个节点和子节点,在子节点上上下移动,请使用ObjectScript `$Query`函数。(或者,可以使用嵌套的`$ORDER`循环)。 **`$Query`函数接受全局变量引用,并返回一个字符串,其中包含全局变量中下一个节点的全局引用(如果没有后续节点,则返回`""`)。若要使用`$QUERY`返回的值,必须使用ObjectScript间接运算符`(@)`。** 例如,假设定义了以下全局设置: ```java Set ^Data(1) = "" Set ^Data(1,1) = "" Set ^Data(1,2) = "" Set ^Data(2) = "" Set ^Data(2,1) = "" Set ^Data(2,2) = "" Set ^Data(5,1,2) = "" ``` 以下是对`$QUERY`的调用: ```java SET node = $QUERY(^Data("")) ``` 将节点设置为字符串`“^Data(1)”`,即全局中第一个节点的地址。然后,要获取全局中的下一个节点,请再次调用`$QUERY`并在节点上使用间接运算符: 此时,节点包含字符串`“^Data(1,1)”`。 以下示例定义一组全局变量节点,然后使用`$QUERY`遍历它们,同时写入每个节点的地址: ```java /// w ##class(PHA.TEST.Global).ReadGlobalSimpleQuery() ClassMethod ReadGlobalSimpleQuery() { Kill ^Data // 确保^Data为空 // 将一些数据放入^Data // Set ^Data(1) = "" Set ^Data(1,1) = "" Set ^Data(1,2) = "" Set ^Data(2) = "" Set ^Data(2,1) = "" Set ^Data(2,2) = "" Set ^Data(5,1,2) = "" // 现在浏览^Data 查找第一个节点 Set node = $Query(^Data("")) While (node '= "") { Write node,! // 获取下一个节点 Set node = $Query(@node) } q "" } ``` ```java DHC-APP>w ##class(PHA.TEST.Global).ReadGlobalSimpleQuery() ^Data(1) ^Data(1,1) ^Data(1,2) ^Data(2) ^Data(2,1) ^Data(2,2) ^Data(5,1,2) ```
文章
姚 鑫 · 一月 20, 2022

第二十九章 SQL函数 COALESCE

# 第二十九章 SQL函数 COALESCE 返回第一个非空表达式的值的函数。 # 大纲 ``` COALESCE(expression,expression [,...]) ``` - `expression` - 要计算的一系列表达式。多个表达式被指定为逗号分隔的列表。此表达式列表限制为`140`个表达式。 # 描述 `COALESSE`函数按从左到右的顺序计算表达式列表,并返回第一个非空表达式的值。如果所有表达式的计算结果都为`NULL`,则返回`NULL`。 字符串返回时保持不变;保留前导和尾随空格。数字以规范形式返回,去掉了前导零和尾随零。 # 返回值的数据类型 非数字表达式(如字符串或日期)必须都属于相同的数据类型,并返回该数据类型的值。指定数据类型不兼容的表达式会导致`SQLCODE-378`错误,并显示`DataType`不匹配错误消息。可以使用`CAST`函数将表达式转换为兼容的数据类型。 数值表达式可以是不同的数据类型。如果指定具有不同数据类型的数值表达式,则返回的数据类型是与所有可能的结果值最兼容的表达式数据类型,即具有最高数据类型优先级的数据类型。 文字值(字符串、数字或`NULL`)被视为数据类型`VARCHAR`。如果只指定两个表达式,则文字值与数值表达式兼容:如果第一个表达式是数值表达式,则返回其数据类型;如果第一个表达式是文字值,则返回`VARCHAR`数据类型。 # 比较NULL处理函数 下表显示了各种SQL比较函数。如果逻辑比较测试为`True`(`A`与`B`相同),则每个函数返回一个值;如果逻辑比较测试为`False`(`A`与`B`不同),则每个函数返回另一个值。这些函数允许执行空逻辑比较。不能在实际相等(或不相等)条件比较中指定`NULL`。 SQL函数 | 比较测试| 返回值 ---|---|--- COALESCE(ex1,ex2,...)| ex = NULL for each argument| True tests next ex argument. If all ex arguments are True (NULL), returns NULL. False returns ex IFNULL(ex1,ex2) [two-argument form] | ex1 = NULL |True returns ex2 False returns NULL IFNULL(ex1,ex2) [three-argument form] | ex1 = NULL | True returns ex2 False returns ex3 {fn IFNULL(ex1,ex2)} | ex1 = NULL | True returns ex2 False returns ex1 ISNULL(ex1,ex2) |ex1 = NULL | True returns ex2 False returns ex1 NVL(ex1,ex2)| ex1 = NULL | True returns ex2 False returns ex1 NULLIF(ex1,ex2) | ex1 = ex2 | True returns NULL False returns ex1 # 示例 下面的嵌入式SQL示例接受一系列主机变量值,并返回第一个非空的(值`d`)。请注意,ObjectScript空字符串(`""`)在 SQL中被转换为`NULL`: ```java ClassMethod Coalesce() { s (a, b, c, e) = "" s d = "firstdata" s f = "nextdata" &sql(SELECT COALESCE(:a,:b,:c,:d,:e,:f) INTO :x) if SQLCODE '= 0 { w !,"Error code ",SQLCODE } else { w !,"The first non-null value is: ",x } } ``` ```java DHC-APP>d ##class(PHA.TEST.SQLCommand).Coalesce() The first non-null value is: firstdata ``` 下面的示例按从左到右的顺序比较两列的值,并返回第一个非空列的值。对于某些行,`FavoriteColors`列为`NULL`;`Home_State`列从不为`NULL`。为了让`Coalesce`将两者进行比较,`FavoriteColors`必须转换为字符串: ```sql SELECT TOP 25 Name,FavoriteColors,Home_State, COALESCE(CAST(FavoriteColors AS VARCHAR),Home_State) AS CoalesceCol FROM Sample.Person ``` 以下动态SQL示例将`COALESCE`与其他`NULL`处理函数进行比较: ```java ClassMethod Coalesce1() { s myquery = "SELECT TOP 50 %ID,"_ "IFNULL(FavoriteColors,'blank') AS Ifn2Col,"_ "IFNULL(FavoriteColors,'blank','value') AS Ifn3Col,"_ "COALESCE(CAST(FavoriteColors AS VARCHAR),Home_State) AS CoalesceCol,"_ "ISNULL(FavoriteColors,'blank') AS IsnullCol,"_ "NULLIF(FavoriteColors,$LISTBUILD('Orange')) AS NullifCol,"_ "NVL(FavoriteColors,'blank') AS NvlCol"_ " FROM Sample.Person" s tStatement = ##class(%SQL.Statement).%New() s qStatus = tStatement.%Prepare(myquery) if qStatus '= 1 { w "%Prepare failed:" d $System.Status.DisplayError(qStatus) q } s rset = tStatement.%Execute() d rset.%Display() w !,"End of data" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQLCommand).Coalesce1() ID Ifn2Col Ifn3Col CoalesceCol IsnullCol NullifCol NvlCol 1 value $lb("Red","Orange","Yellow") $lb("Red","Orange","Yellow") $lb("Red","Orange","Yellow") $lb("Red","Orange","Yellow") 2 blank blank blank blank 6 blank blank blank blank 9 blank blank blank blank 10 blank blank blank blank 13 value $lb("Red","Orange","Yellow","Green") $lb("Red","Orange","Yellow","Green") $lb("Red","Orange","Yellow","Green") $lb("Red","Orange","Yellow","Green") 14 value ReOrangYellowGreen $lb("Red","Orange","Yellow","Green","Green") $lb("Red","Orange","Yellow","Green","Green") $lb("Red","Orange","Yellow","Green","Green") 15 value ReOrangYellowGreen $lb("Red","Orange","Yellow","Green","Yellow") $lb("Red","Orange","Yellow","Green","Yellow") $lb("Red","Orange","Yellow","Green","Yellow") 16 value $lb("White") $lb("White") $lb("White") $lb("White") 17 value $lb("Black") $lb("Black") $lb("Black") $lb("Black") 18 value $lb("Green","White") $lb("Green","White") $lb("Green","White") $lb("Green","White") 19 value $lb("Purple") $lb("Purple") $lb("Purple") $lb("Purple") 20 value $lb("Yellow") $lb("Yellow") $lb("Yellow") $lb("Yellow") 21 value $lb("Red","Red") $lb("Red","Red") $lb("Red","Red") $lb("Red","Red") 22 value $lb("Black","Black") $lb("Black","Black") $lb("Black","Black") $lb("Black","Black") 23 blank blank MT blank blank 24 value $lb("Blue") $lb("Blue") $lb("Blue") $lb("Blue") 25 blank blank WY blank blank 26 value $lb("White") $lb("White") $lb("White") $lb("White") 27 value $lb("Orange") $lb("Orange") $lb("Orange") 28 value $lb("Orange","White") $lb("Orange","White") $lb("Orange","White") $lb("Orange","White") 29 value $lb("Black") $lb("Black") $lb("Black") $lb("Black") 30 value $lb("Red","Green") $lb("Red","Green") $lb("Red","Green") $lb("Red","Green") 31 value $lb("Purple") $lb("Purple") $lb("Purple") $lb("Purple") 32 value $lb("Green","Red") $lb("Green","Red") $lb("Green","Red") $lb("Green","Red") 33 blank blank TX blank blank 34 value $lb("Blue","Green") $lb("Blue","Green") $lb("Blue","Green") $lb("Blue","Green") 35 value $lb("Yellow") $lb("Yellow") $lb("Yellow") $lb("Yellow") 36 value $lb("Red","Blue") $lb("Red","Blue") $lb("Red","Blue") $lb("Red","Blue") 37 blank blank HI blank blank 38 blank blank OR blank blank 39 blank blank NM blank blank 40 blank blank NJ blank blank 41 blank blank NY blank blank 42 value $lb("Black","Orange") $lb("Black","Orange") $lb("Black","Orange") $lb("Black","Orange") 43 value $lb("Blue") $lb("Blue") $lb("Blue") $lb("Blue") 44 blank blank MO blank blank 45 value $lb("Purple","Yellow") $lb("Purple","Yellow") $lb("Purple","Yellow") $lb("Purple","Yellow") 46 value $lb("Orange") $lb("Orange") $lb("Orange") 47 value $lb("Yellow","Green") $lb("Yellow","Green") $lb("Yellow","Green") $lb("Yellow","Green") 48 value $lb("Blue","Blue") $lb("Blue","Blue") $lb("Blue","Blue") $lb("Blue","Blue") 49 value $lb("Blue") $lb("Blue") $lb("Blue") $lb("Blue") 50 value $lb("White","Red") $lb("White","Red") $lb("White","Red") $lb("White","Red") 51 value $lb("White","Green") $lb("White","Green") $lb("White","Green") $lb("White","Green") 52 blank blank MT blank blank 53 value $lb("Red") $lb("Red") $lb("Red") $lb("Red") 54 blank blank MD blank blank 55 value $lb("Orange","Orange") $lb("Orange","Orange") $lb("Orange","Orange") $lb("Orange","Orange") 56 blank blank MD blank blank 57 value $lb("White") $lb("White") $lb("White") $lb("White") 50 Rows(s) Affected End of data ```
文章
Claire Zheng · 六月 19

2025.1 打造时尚现代的互操作性用户体验

互操作性用户界面现在包括可以在所有互操作性产品中使用的 DTL 编辑器和生产配置应用程序的现代化用户体验。您可以在现代化视图与标准视图之间切换。所有其他互操作性屏幕仍采用标准用户界面。请注意,仅对这两个应用程序进行了更改,我们在下面确定了当前可用的功能。 要在升级前试用新屏幕,您可以点击这里,从我们的社区工具包网页中下载 2025.1 版:https://evaluation.intersystems.com/Eval/。请观看“学习服务”中的简短教程构建集成:一种新的用户体验,了解对这些屏幕进行的用户增强! 生产配置 - 配置任务简介 生产配置:在以下版本的生产配置中受支持: 创建/编辑/复制/删除主机 停止/启动主机 编辑生产设置 停止/启动生产 源代码控制集成:支持上述配置功能的源代码控制集成。 分屏 视图:用户可以直接从“生产配置”屏幕打开“规则编辑器”和“DTL 编辑器”,在分屏视图中查看和编辑产品中包含的规则和转换。 增强的筛选功能:使用顶部的搜索框,您可以搜索和筛选各种业务组件,包括多种类别、DTL 和子转换。 使用左侧边栏可以独立于主面板进行搜索,查看各种主机和类别中的搜索结果。 批量编辑主机类别:通过从生产配置中添加主机,您可以为生产添加新类别或编辑现有类别。 可展开路由器:可以展开路由器,内联查看所有规则、转换和连接。 重新设计的主机连接:现在,在选择业务主机时,将呈现直接连接和间接连接,您可以查看消息能够采用的完整路径。 将鼠标悬停在任何出站或入站主机上可以进一步区分连接。如果开启仅显示连接的主机开关,将仅筛选所选主机及其连接。 DTL 编辑器 - DTL 工具简介 源代码控制集成:支持源代码控制集成。 VS Code 集成:用户可以在其 VS Code IDE 中查看此版本的 DTL 编辑器。 嵌入式 Python 支持:此版本的 DTL 编辑器现在支持嵌入式 Python。 DTL 测试:可以在此版本的 DTL 编辑器中使用 DTL 测试实用工具。 切换面板布局:DTL 编辑器支持侧面到侧面和顶部到底部布局。 点击顶部功能区的布局按钮可以体验此功能。 撤消/重做:用户可以使用撤消/重做按钮撤消和重做所有尚未保存为代码的操作。 “生成空段”参数:GENERATEEMPTYSEGMENTS 参数可用于为缺失的字段生成空段。 子转换查看:用户可以点击眼睛图标,在新选项卡中打开子转换 DTL,查看子转换。 滚动: 单独滚动:将光标放置在 DTL 的左右两部分(源和目标)其中之一的上方,并用滚轮或触控板垂直移动各段,可以单独滚动各个部分。 联合滚动:将光标放置在图的中间,可以联合滚动源部分和目标部分。 字段自动补全:自动补全适用于:“源”、“目标”和“条件”字段以及源类、源文档类型、目标类、目标文档类型。 顺序编号:使用可视化编辑器,您可以打开和关闭查看每个段的序数和完整路径表达式的功能。 轻松引用:当操作编辑器中的某个字段获得焦点时,在图形化编辑器中双击某个段会在操作编辑器中的当前光标位置插入相应的段引用。 同步:点击可视化编辑器中的一个元素,可以在操作编辑器中高亮显示相应的行。 📣号召性用语📣 如果您有任何反馈,请通过以下途径提供给我们: ✨跨所有互操作性的新功能:在 Ideas 门户中输入想法,或在 InterSystems Ideas 门户中参与其他想法。 对于新想法,请在您的帖子上添加“互操作性”标签或对列表中已提出的功能进行投票! 💻跨所有互操作性的一般用户体验反馈:请在下面输入您的反馈或参与其他评论。 🗒对现代化应用程序的建议/反馈(如上所述):请在下面输入您的反馈或参与其他评论。 请考虑利用 Global Masters 的机会,与团队进行互动,参与不公开的指导反馈会议,并获得积分! 点击此处,通过 Global Masters 报名参加这些会议。 如果您希望以私人形式提供任何其他反馈,请通过电子邮件将您的想法或问题发送至:ux@intersystems.com
文章
Claire Zheng · 八月 17, 2021

FHIR标准和国际基于FHIR的互联互通实践(3):常见的互操作范式

实现互通的方式方法有很多种,我们通常会见到4种:消息交换、文档交换、服务和 API。 消息交换是最常见的一种互操作的方式,在医疗用得非常多。消息交换是基于消息引擎的,通常它应用在低业务集成度和跨数据管理域的业务环境里面。通常消息是基于临床事件,描述临床事件发生的上下文,并且能够在临床事件发生的时候,通过消息引擎把它路由给消息的接收方。 消息交换的本质是一个中心化的互操作方式,中心化的方式也能够保证消息的先进先出。我们常见的消息标准有HL7 V2、 HL7 V3的消息。 文档交换是另外一种常见的互操作方式。有别于基于临床事件的消息,文档是一种阶段性的、小结性的、完整的医疗信息的汇总。它的应用场景也是低业务集成度、跨数据管理域的环境,不过通常情况下,文档交换通常都是用在机构之间的,而不是在一家医疗机构内部的不同业务系统之间。可以通过消息引擎,当然也可以使用其他的方式来进行相应的交换。文档的标准,我们最常见的有HL7 CDA。 服务的交换是另外一种。服务是什么?服务是封装好的,并且暴露出来的一组内聚的应用系统的功能。基于服务交互的互操作,需要双方规范互操作的业务流程和角色。服务交互通常是基于面向服务的这种架构、通过服务总线来进行交互的,也是应用在低业务集成度和跨数据管理的业务环境。服务是基于规范的业务流程、角色的,但是在医疗行业并不是所有的医疗流程都已经或者是能够规范的,所以服务交互目前来看还是有一定的适用范围。最常见的国际上的服务标准是IHE。 API是现在最流行的。API在当今的网络经济里已经是无处不在了。它是概述了一组明确定义的规范,允许应用程序能够建立在另一个应用程序已有的数据和功能之上,而且不需要去了解其他的应用系统的系统设计。有别于消息和文档,API可以仅传输必要的信息,效率比较高,不需要传完整的上下文,所以它不需要中心化的消息,本质上是一个去中心化的架构,比较适合于业务集成度更紧密的互操作的场景。 目前面临的是传统的医疗边界被突破了——例如互联网医院这样的使用场景——我们需要扩大互操作性、互操作对象和数据,而这也驱动了医疗API的出现,这也是我这次介绍的一个重点,HL7 FHIR标准。 注:本文根据InterSystems中国技术总监乔鹏演讲整理而成。
文章
王喆 👀 · 十月 18, 2022

COS的基本语法

ObjectScript是一种面向对象的编程语言,它是InterSystems公司的Caché和Ensemble数据库的核心语言之一。ObjectScript语言的语法类似于MUMPS语言,它支持面向对象编程、过程式编程、函数式编程等多种编程范式。ObjectScript语言主要用于开发Caché和Ensemble数据库应用程序,它可以访问数据库中的数据、调用数据库中的存储过程、触发器和事件,还可以与其他编程语言进行交互。 Cache使用的语言是ObjectScript简称COS,下面展示的是其基本语法,也是我个人的COS字典: 1 系统指令 SET 缩写 s ,赋值命令,样例 - s hello ="Hello World"; WRITE 缩写 w ,向当前设备输出,样例 - w hello (特殊用法:w ! 换行、w # 清屏 ) DO 缩写 d ,执行函数,样例 – d ##class(%SYSTEM.License).ShowSummary(); Kill 缩写 k ,从堆栈中清楚变量 x,慎用(不加参数调用时候将清楚内存中的所有变量!)样例 - k x Quit 缩写 q , 返回 样例 - q $$$OK 注意:系统指令不区分大小写,变量和对象大小写敏感 2 数据类型 %Integer 整数型 0,1,2,3,4,5,6...... %Boolean 布尔类型 0-F 1-T %String 字符串类型 "你好,世界" %Date 时间类型 更多的数据类型: https://docs.intersystems.com/healthconnect20201/csp/docbook/DocBook.UI.Page.cls?KEY=GOBJ_proplit#GOBJ_C27523 3 运算符 数学运算符 加减乘除+,-,*,/ W 2+3+2*2 14 运算时严格从左到右执行,数学运算没有优先级 W 2+3+2*2 9 连字符_ W "shanghai"_"disney" 逻辑运算符 与或非&, ||, ‘ W 1&0 4 流程控制 For 循环 FOR variable=start:increment:end { . . . } #; 第1种 For i = 1:1:9 { Write i, ! } #; 第2种 Set key = "" For { Set key = $ORDER(array(key)) Quit:key="" // process array(key) } #; 第3种 For value = "Red","Green","Blue" { Write value, ! } If else判断 IF expression1 { . . . } ELSEIF expression2 { . . . } ELSE { . . . } If a>0{ w a }ElseIf a<0&&a=-1{ w a }ElseIf a<-1{ w a }else{ w a } While循环 WHILE expression,... { ;. . . } #; 第1种 Do { } While (1 /* condition */) #; 第2种 While (1 /* condition */) { } 5 系统变量 当前时间 $Horolog — W $H 时间戳 $ZTIMESTAMP — w ZTS 系统版本 $ZVERSION — W $ZV 注意:系统变量不区分大小写 更多系统变量 https://docs.intersystems.com/healthconnect20201/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_VARIABLES 6 函数 字符串类型与日期类型相互转换: $ZDH/$ZD — w $ZDH("2017-03-13",3) — w $zd(0) 字符串截取函数: $piece — W $P("This is training", " ",3) 字符串比较替换函数: $Case — W $case(10,1:"100",2:"200",:"1000") 按值查找,并返回一个整数,该整数指定子字符串中的结束位置 $FIND w $f("acvs","c",1) 输出为字符串的位置,输出为0标识不存在 更多函数: https://docs.intersystems.com/healthconnect20201/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_FUNCTIONS 7 Global Global算是IRIS的一个特殊概念,其实可以把它理解为可以持久化的多维数组,下面展示的是把Global当成多维数组的处理方式 下标:可以是数字也可以是字符串 操作:赋值 - set(s) 、删除 - kill(k) 持久化:多维数组命名时候以 ^ 开头,会按下标存储,如果用的是HealthConnect或者IRISHealth在【系统资源管理器】- 【Global】中可以看到。 样例: s a = 1,a(1) = "a",a(1,1) = "b",a(1,1,"wow") = "foo",a(1,2) = "c",a(2) = 0 zw a (这里,的意思代表省略了前面的 set 系统指令)如图: 这是我早些时候的笔记,可能至今还有一些错误,希望有看出问题的大佬指正。 非常棒的分享!
文章
Hao Ma · 四月 16, 2024

IRIS/Caché SQL优化经验分享 - SQL索引分析器

索引分析器工具用来分析索引的使用情况,对DBA和开发者非常有用。 他们需要知道那些查询进行了全表扫描,那些查询缺失了索引, 而那些索引从来又从来没有被用过。多余的索引降低系统性能,浪费了磁盘空间。 **索引使用情况** 到“管理门户”的" 系统 > SQL 性能工具 > SQL 索引分析器", 点击**“索引使用情况”**, 您将看到这样的图 执行SQL语句查询会带来更多的灵活性。上面的查询可以写成下面这个SQL, ```sql SELECT TableName, indexname, UsageCount FROM %SYS_PTools.UtilSQLAnalysisDB order by usagecount desc ``` 2016年以后的Caché版本就已经有了'索引使用情况'的查询。使用管理门户没有区别, 但SQL语句不同,使用的是比较老的类和表名,各位请参考文档。 注意上图中另外几个按钮,它们的介绍在文档的[这个链接](https://docs.intersystems.com/irisforhealth20241/csp/docbook/DocBook.UI.Page.cls?KEY=GSOD_indexes#GSOD_indexes_analyze_tool_reportoptions), 简单的做个翻译: **全表扫描的查询**: 可识别当前命名空间中进行全表扫描的所有查询。应尽可能避免全表扫描。全表扫描并非总能避免,但如果某个表有大量全表扫描,则应检查为该表定义的索引。通常情况下,表扫描列表和临时索引列表会重叠;修复一个会移除另一个。结果集列出了从最大块计数到最小块计数的表。[显示计划](https://docs.intersystems.com/irisforhealth20241/csp/docbook/DocBook.UI.Page.cls?KEY=GSOE_interpret#GSOE_interpret_showplan)链接可显示语句文本和查询计划。 **使用临时索引的查询**: 该选项可识别当前命名空间中所有建立临时索引以解析 SQL 的查询。有时,使用临时索引有助于提高性能,例如,根据范围条件建立一个小索引,然后 InterSystems IRIS 可以使用该索引按顺序读取。有时,临时索引只是不同索引的子集,可能非常高效。其他时候,临时索引会降低性能,例如,扫描主MAP以在有条件的属性上建立临时索引。这种情况表明缺少一个所需的索引;你应该在类中添加一个与临时索引匹配的索引。结果集列出了从最大块计数到最小块计数的表。[显示计划](https://docs.intersystems.com/irisforhealth20241/csp/docbook/DocBook.UI.Page.cls?KEY=GSOE_interpret#GSOE_interpret_showplan)链接可显示语句文本和查询计划。 **缺少JOIN索引的查询**: 该选项会检查当前命名空间中所有使用JOIN的查询,并确定是否定义了支持该JOIN的索引。它将可用来支持JOIN的索引从 0(无索引)排到 4(索引完全支持JOIN)。外关联需要单向索引, INNER JOIN需要两个方向的索引。默认情况下,结果集中只包含 JoinIndexFlag < 4 的记录。 JoinIndexFlag=4 表示有一个完全支持JOIN的索引。 **具有离群值Outlier索引的查询**: 该选项可识别当前命名空间中所有具有[异常值的](https://docs.intersystems.com/irisforhealth20241/csp/docbook/DocBook.UI.Page.cls?KEY=GSOD_opttable#GSOD_opttable_tunetable_selectivity)查询,并确定是否定义了支持异常值的索引。它将可用来支持异常值的索引从 0(无索引)到 4(索引完全支持异常值)进行排序。默认情况下,结果集中只包含 OutlierIndexFlag < 4 的记录。 OutlierIndexFlag=4 表示有一个完全支持异常值的索引。
文章
Hao Ma · 五月 24, 2023

使用Manifest

Manifest也许应该被翻译成“清单”, 字典上是这么解释的: 提供船舶及其货物和其他物品、乘客和船员的全面细节的文件,供海关官员使用,比如:飞机上的乘客或货物清单; 一辆货运列车的车厢清单。 在计算机语言中, Manifest可以是各种格式,用的最多的是xml和json,在IRIS中,manifest是xml格式的, 放在objectscript类的XDATA块里。 ## 编写mainfest IRIS用manifest来做配置。内部工具*%install*, 会读取manifest, 生成真正的objectscript代码来配置IRIS。我们来看个基本的例子。 ### 基本用法 下面的User.Manifest.cls` ,它配置了IRIS的global buff, bbsize等等, 然后还创建了一个命名空间。 ```java Include %occInclude Class User.Manifest { ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 3, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ] { Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "MyInstall") } XData MyInstall [ XMLNamespace = INSTALLER ]{ } } ``` 稍微解释一下代码: - `Include %occInclude`是必须的 - `setup()`用来读取manifest的内容,完成配置工作。用户基本不用修改这个method。 - mainifest本身的逻辑层次很清楚,要配置什么内容查查文档都可以。上面的manifest只是个示意,真正用起来可以需要非常多的配置项,比如namespace, database的配置,有很多的标签可选。 ### 传参数给manifest 调用manifest的method, 也就是例子里的setup(), 注意第一个参数是`ByRef pVars`。这是objectscript里常用的By referrence的传参方式。请看下面的例子: ```java Include %occInclude Class User.Manifest { ClassMethod main(){ Set pVars("Namespace")="MYNAMESPACE" Set pVars("AnotherKey")= "AnotherValue" $$$ThrowOnError(..CreateNamespace(.pVars)) } ClassMethod CreateNamespace(ByRef pVars, pLogLevel As %Integer = 3, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ]{ Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "CreateNamespace") } XData CreateNamespace [ XMLNamespace = INSTALLER ]{ } } ``` 上面code在main()里定义了一个pVars, 放了两个key-value, 用来调用CreateName()。 定义的namespace在manifest用到了, Anotherkey被用在了log里, 只是一个示意。 注意这个定义: `Dir="${MGRDIR}/${Namespace}"`。 其中的`MGRDIR`不需要自己定义。Manifest有一堆自己定义的Variable, 用的最多的是 CFGDIR, CSPDIR, INSTALLDIR, MGRDIR, PORT等等。具体列表见文档。 ### 更多的用法 下面这个例子包括了import文件和copy文件, *SourceDir*和*Namespace*是传入的参数。导入文件要在一个namespace的定义里面, 拷贝文件和命名空间无关。 ```xml ``` **CSPApplication** 在manifest里定义cspapplication不难,麻烦的是不同的版本使用的标签上会有修改。下面给了一个例子。 ```xml ``` > **调用代码的例子** ```xml ``` **创建User, Role** ```xml ``` Nampespace Mapping ```xml ``` 其他还有很多的用法。想了解更多,可以看看[文档说明里的Tag列表](https://docs.intersystems.com/iris20223/csp/docbook/DocBook.UI.Page.cls?KEY=GCI_manifest#GCI_manifest_tags), 和[template](https://docs.intersystems.com/iris20223/csp/docbook/DocBook.UI.Page.cls?KEY=GCI_manifest#GCI_manifest_example_template)。 ## 怎么执行cls文件 [Using the Manifest](https://docs.intersystems.com/iris20223/csp/docbook/DocBook.UI.Page.cls?KEY=GCI_manifest#GCI_manifest_invoke) **在Terminal里执行** ```zsh %SYS>do ##class(MyPackage.MyInstaller).setup() ``` 或者, 带上参数 ``` %SYS>set vars("SourceDir")="c:\myinstaller" %SYS>set vars("Updated")="Yes" %SYS>do ##class(MyPackage.MyInstaller).setup(.vars,3) ``` **During IRIS安装**? Export the manifest class as DefaultInstallerClass.xml to the same directory where the InterSystems IRIS install (either .msi, setup_irisdb.exe, or irisinstall) is run. It is imported into %SYS and compiled, and the setup() method is executed. 那么是irisinstall里面的什么语句在执行manifest呢? ```bash # script, 用ISC_INSTALLER_MANIFEST, installer-manifest-example.xml [root@silent jhvinstall]# cat cache_install.sh #!/bin/bash echo -n "Installing Cache..." ISC_INSTALLER_MANIFEST=$3 ISC_PACKAGE_INSTANCENAME=$1 ISC_PACKAGE_INSTALLDIR=$2 ISC_PACKAGE_UNICODE="Y" ISC_PACKAGE_INITIAL_SECURITY="Minimal" ISC_PACKAGE_MGRUSER="cacheusr" ISC_PACKAGE_MGRGROUP="cacheusr" ISC_PACKAGE_USER_PASSWORD="sys" ./cinstall_silent [root@silent jhvinstall]# ./cache_install.sh CACHE6 "/cache/tmpcache6" "/tmp/installer-manifest-example.xml" Installing Cache... ``` **写一个脚本执行** 这里给一个脚本的例子,简短,但内容很丰富。 ```bash #!/bin/bash # Usage install.sh [instanceName] [password] instanceName=$1 password=$2 DIR=$(pwd) ClassImportDir=$DIR/install NameSpace="ENSDEMO" CspPath="/csp/ensdemo" SrcDir=$DIR/src/CLS DirFront=$DIR/src/CSP/csp/demo irissession $instanceName -U USER
文章
Claire Zheng · 一月 4, 2023

【CHIMA报道】香港大学深圳医院:通过一体化系统推动数字化转型与智慧医院建设

2022年,香港大学深圳医院迎来建院十周年。作为深港合作医疗平台和公立医院改革试点,医院是由深圳市政府全额投资,并引进香港大学现代化管理模式的大型综合性公立医院。十年来,医院全力打造集“医、教、研、管”为一体的四个粤港澳大湾区国际化中心,2021年7月成为国家公立医院高质量发展试点医院,2021年12月成为国家建立健全现代医院管理制度试点医院。目前,医院正在探索改革创新路径,全面配合公立医院高质量发展对信息化建设提出的需求。 香港大学深圳医院设立了绿色医院建设内涵,从绿色生态建筑的设计、到建筑智能化的应用、再到各类医疗信息系统的实施,医院践行可持续发展的建设理念。数字化转型是公立医院医院高质量发展的必由之路。十年间,医院在打造公立医院高质量发展路径过程中,借助信息技术推动数字化转型,探索智慧医院建设,不断提升医疗质量和效率,优化医院内部医疗资源配置,改善人民群众就医感受。 推行绿色医疗,开展数字化转型 作为深圳市首家去编制的公立医院,自成立伊始,香港大学深圳医院以“绿色医疗理念”为指导,引入香港公立医院管理模式,成为深圳市乃至全国公立医院改革的先锋。 香港大学深圳医院的绿色医疗涵盖八大主题:绿色办医,公平公正;绿色管医,廉洁高效;绿色行医,专业行政;绿色治安,持续改进;绿色文化,关爱慈善;绿色科技,智慧领跑;绿色建筑,节能降耗。 其中,“绿色科技,智能领跑”这一主题涉及到信息化建设,香港大学深圳医院通过构建一体化系统,探索出数字化转型四条特色路径。 1.通过新一代集成平台构建一体化业务系统 医院开业之初,采用了7+1的集成平台系统架构模式,为医院发展提供了重要的信息化支撑。但是该集成平台设计较早,在流程中没有考虑队列问题,不利于数据流通。同时,旧系统每天的消息吞吐量达到500多万条,要求每分钟每个业务系统处理要达到200条才能处理完,否则会对数据传输造成影响。基于此,医院期望在高效传输协议和标准化方面做一些探索和实践。经过深入调研后,医院于2021年1月份启动新一代集成平台建设。新一代集成平台于2021年8月份整体上线,构建了以InterSystems高性能数据平台为核心的一体化业务系统,新一代集成平台通过InterSystems数据平台先进的互操作能力、高度可扩展能力以及对国际数据标准(如HL7 V2和FHIR)的深度支持,实现了数据的标准化、规范化和一体化,加强了患者主索引(EMPI)和主数据词典(MDM)的建设,进一步完善了患者信息档案,实现了统一身份登录认证,推动数据互联互通和共享。 医院信息平台技术架构 香港大学深圳医院的集成平台分为内网和外网,两者数据是互通的,通过授权、安全访问机制、API管理,向患者提供标准化医疗服务。主要有两方面的业务协同。 第一是院内业务系统协同,是业务信息系统交换的核心组件。内网通过多种传输协议和技术,在不改变原有业务系统自身协议的情况下,进行数据对接和互联互通。其主要核心功能包括数据在消息流转的过程中,完成数据整个格式和内容的转换和调整。比如新一代集成平台可以把HL7 V2的协议转换为FHIR的文档,当医生在日常工作中需要浏览检查检验申请单或传输患者病历时,因病历内容很长,如果用HL7 V2或V3协议,传输的数据量非常大,传输也会比较慢。借助新一代集成平台,医院可以将需要传输的内容通过FHIR进行转换,从而将传输内容大幅缩短,大幅提高了传输效率,使数据流转更畅通。 InterSystems对FHIR的深度支持可以让香港大学深圳医院无缝地完成多数据模型的格式转换,从而实现整个异构数据类型的整合以及不同应用系统之间的交互服务,缩短了业务开发周期,聚焦到业务应用。 院内总线 第二,互联网业务和院内业务系统的协同,需要整合应用系统的数据或服务,实现单个或多个应用的场景。和单纯院内系统协同不同的是,当互联网医院的业务涉及和第三方系统以及互联网医疗一些商用平台对接的时候,医院一般不开放直接数据库,都是通过接口的方式,在内网的集成平台和外网的集成平台进行数据交互,再通过外网的集成平台提供对外的数据访问服务,InterSystems也提供了安全访问机制,用于平台对外提供各种数据服务。 院外总线 2.探索数字化转型四条路径 作为首家深港合作医院,医院的立足点是以深圳为中心,借助信息技术,向粤港澳大湾区的疑难重症患者提供高质量的诊疗救治服务。依据这一立足点,香港大学深圳医院探索了数字化转型的四条路径。 第一,基于目前已经开展的港澳药械通服务,在全国首次探索实现跨境药械SPG供应链追溯数据互联互通。目前医院建立了跨境药械SPG供应链,将国外药企的生产企业、经营企业、第三方物流公司,和医院的三方供应链联通,构建供应链平台,将供应的物资及时提供给医院,积极开展精细化的运营管理,包括跨界药械的申请、采购、进口、配送、使用以及不良反应监测的全过程监控、追溯,实现大湾区临床急需药品器械来源可溯,去向可追,使用可控,责任可纠。 第二,跨境医疗服务的健康数据共享。这个主要是探索跨境医疗服务的应用模式,目前医院和香港医管局在进行一个名为在院患者特别支援诊疗计划的合作。该合作于2020年11月份启动,由香港医管局授权,医院对在粤的香港患者进行后续诊疗复诊服务。香港医管局会把这些患者的病历提供给医院,实现了整个医疗健康档案的跨境应用,推进整个粤港澳湾区一体化的健康数据共享以及互联互通。 第三,加速推动新兴技术和医疗服务的深度融合。医院借助大数据、互联网、AI语音等信息技术,依托云计算、5G通信技术,并基于现有的院内信息系统,构建统一的数据存储平台。同时,医院还探索了全语音驱动操作机器人在智慧病房的应用。 第四,推动数据的创新应用。主要是结合医院重点医疗学科建设,把就诊的每个环节标准化、规范化,包括拆分和数字化。医院结合先进的信息技术,对这些数据提炼应用,帮助院内各流程提能、增效。医院也会和一些大学、企业、研究机构合作,形成产、学、研合作中心,把每个治疗环节和整个服务,应用信息技术,提升到较高的境界,为医院医疗服务提供更多支撑。 香港大学深圳医院在进行数字化转型过程中,逐步探索出符合医院管理模式、运营模式和业务模式的智慧医院建设方式,主要包括以下三点:第一,以智慧医院建设为抓手,加快和推进公立医院的高质量发展;第二,以信息化技术手段为路径,全面推动很多业务落地;第三,以远程医疗服务为支点,推进医疗服务的融会贯通,树立粤港澳大湾区医疗服务创新标杆。 规划智慧医院建设四个方向 在逐步推动医院数字化转型过程中,香港大学深圳医院根据公立医院高质量发展的要求,开展了三位一体智慧医院建设,打造具有中国特色的国内顶尖、国际一流的智慧医院样板,实现了流程无纸化、业务智能化、管理精细化、服务人性化。医院规划了智慧医院建设的四个方向。 第一,针对智慧医疗,围绕电子病历评级应用水平分级、以及互联互通的标准化成熟度等级评测等要求进行信息化建设,开展以评促建的升级改造服务。通过信息技术,实现院内流程闭环、互联互通和信息共享,完善医疗服务体系,构建线上线下、院内院外一体化医疗服务体系。 第二,针对智慧服务,将5G技术贯穿于整个院前、院中、院后的诊疗环节,加快5G在疫情预警、院前急救、实施会诊、远程手术以及医院和香港的跨境医疗等智慧医疗服务的应用,打造未来创新医疗服务中心。 第三,针对智慧管理,建设数字化医疗质量评价促进中心,上线国家三级公立医院绩效考核系统和智慧后勤的楼宇监测系统,加强运营管理监测,进行全方位驱动医院管理效能的提升。 第四,结合大数据、人工智能等新兴技术构建医院的数字大脑,建设医疗运营数据中心、科研数据中心和影像数据中心的主题数据库,设立多维度早期预警,实现敏感指标的监控,为智慧医院建设平台提供支撑。 通过这四项措施,香港大学深圳医院实现了智慧管理,构建了数字孪生的智慧医院。 未来,香港大学深圳医院将按照“高起点、高水平、高质量”的标准持续推进数字化转型,开展下一个十年的智慧医院建设:通过运用大数据、云计算、AI人工智能、物联网、移动互联网、5G等新技术,医院将持续创新各类医疗应用场景,实现“流程无纸化、业务智能化、管理精细化、服务人性化”的高质量医院管理体系;通过高质量医院建设,构建连接、共享、协同的粤港澳大湾区医疗服务体系,探索医疗改革,促进湾区融合。 如何应对医院信息科日常面临的两大挑战 系统选型以及团队建设是医院信息科日常面临的两大挑战,香港大学深圳医院也给出了自己的特色践行方案: 1.如何进行系统服务商选型 香港大学深圳医院信息系统选型标准有四个: 第一, 在医疗信息化领域位列前三甲,并在行业深耕的HIT厂商,通常排名靠前的厂商具有更好的专业背景、行业前瞻性以及更为完整的解决方案; 第二, 在本地有完善的实施和售后团队的,能够及时响应医院需求,并对医院需求进行定制化开发;更重要的是,成熟的实施和售后团队能够将业务和技术互联互通,协助信息科有效甄别临床的“真伪需求”,提供恰当的解决方案; 第三, 有专业和强大的技术研发团队,保障该厂商在医疗行业的可持续投入和发展; 第四, 双方要建立深入的战略合作,确保对系统/信息化目标达成共识,形成合力。 2.如何进行进行信息科的团队建设 香港大学深圳医院负责信息化建设的部门命名为信息资讯科技部(Information Technology Department),团队共26人,分为软件服务组、硬件服务组、数据安全组、项目管理组和行政服务组5个小组。在团队建设方面,信息资讯科技部负责人主要采取了以下措施: 第一, 把握好自己的角色,了解团队成员的性格、能力和习惯,以人为本进行任务安排; 第二, 有效的团队沟通,和团队成员建立良好的沟通机制,授人以渔,给予充分的发展空间,通过每个小组A、B角色安排,兼顾任务与个人能力发展; 第三, 掌握好批评和表扬,通过表扬激发团队的斗志,通过数据说话呈现工作欠缺,批评不针对个人,而是针对事件,指出调整方向; 第四, 对团队成员充分信任,同时让每位团队成员树立责任感;真正做到用人不疑,疑人不用; 第五, 合理分配和安排工作,保障工作完成的时间和质量。时间紧急的,需要找经验丰富的团队成员完成,时间不紧急的可以找希望往这方面发展的团队成员。
公告
Michael Lei · 七月 1, 2022

来自2022全球用户大会--最新在线培训内容上线

欢迎点击 2022在线培训6月刊, 查看我们在全球峰会上发布的新版本--包括基于云服务的在线培训, 此外,获得关于执行分析查询的最新信息,查看最新的认证考试,并尝试新的文档搜索功能。
文章
Lilian Huang · 七月 9, 2023

Docker简介

您好!社区的各位老师, 在本文中,我们将学习以下主题: 什么是 Docker? Docker 的一些好处 Docker 是如何工作的? Docker 镜像 Docker容器 Docker 镜像存储库 InterSystems 的 Docker 镜像存储库 Docker安装 Docker 基本命令 使用 docker 运行 IRIS 社区版 Docker 桌面图形用户界面 那么让我们开始吧。 1.什么是Docker? Docker 是一种虚拟化软件,可以让应用程序的开发和部署变得非常简单。 Docker 通过将应用程序打包到所谓的容器中来实现此目的,该容器保留应用程序运行所需的所有内容,包括应用程序的实际代码、其库和依赖项、运行时和环境配置。 Docker 是一个容器化平台,允许开发人员在容器化环境中创建、部署和运行应用程序。 Docker 提供了一种将应用程序及其依赖项打包到单个容器中的方法,该容器可以在任何支持 Docker 的计算机上运行。这使得创建可快速、轻松部署的可移植、轻量级应用程序变得容易。 2. Docker 的一些好处 您可以在下面找到使用 Docker 的一些好处: 可移植性Docker 容器可以在任何支持 Docker 的机器上运行,从而可以轻松地跨不同环境部署应用程序。 一致性通过将应用程序及其依赖项打包到容器中,Docker 可确保应用程序一致运行,无论底层基础设施如何。 可扩展性Docker 通过运行同一容器的多个实例,可以轻松地水平扩展应用程序。 资源效率Docker 容器是轻量级的,需要最少的资源,这使得它们非常适合在云基础设施上运行。 安全性Docker 为运行应用程序提供了安全且隔离的环境,降低了与其他应用程序或主机系统发生冲突的风险。 3.Docker是如何工作的? Docker 为称为容器的应用程序创建虚拟化环境。容器是一个轻量级、独立的可执行包,包含运行应用程序所需的所有内容,包括代码、库和依赖项。容器与主机系统隔离。因此,它们可以在任何支持 Docker 的机器上运行,无论底层操作系统或硬件如何。 容器是从映像创建的,这些映像是定义应用程序及其依赖项的只读模板。这些镜像存储在称为注册表的中央存储库中,例如 Docker Hub 或私有注册表。开发人员可以自己创建自定义映像或使用注册表中的预构建映像。 当容器启动时,它是从映像构建的,并拥有自己的隔离文件系统、网络和进程空间。然后,容器可以运行应用程序,就像在专用服务器上运行一样。 4. Docker 镜像 Docker 映像是一个轻量级、独立的可执行包,它保留执行应用程序所需的所有内容,包括代码、库和依赖项。 Docker 镜像用于构建和运行容器,容器是可用于运行应用程序的隔离环境。 Docker 映像是根据 Dockerfile 构建的,Dockerfile 是一个文本文件,其中包含一组用于构建映像的指令。 Dockerfile 指定基础映像、应用程序代码和依赖项、环境变量以及创建映像所需的其他配置选项。 Docker 镜像存储在注册表中,例如 Docker Hub 或私有注册表。每次从映像创建容器时,它都会在主机上作为单独的进程运行,与其他进程和容器隔离。 Docker 镜像可用于在不同平台上以一致的方式部署应用程序。它们使打包、分发和部署应用程序变得容易,并确保它们在任何地方都以相同的方式运行。 5.Docker容器 镜像的运行实例是一个容器,如上所述,它是一个轻量级的、独立的、可执行的包,其中包含运行应用程序所需的所有内容,包括代码、库和依赖项。 Docker 容器为运行应用程序提供了一个隔离的环境,确保它拥有正确运行所需的所有资源。每个容器在主机上作为单独的进程运行,并拥有自己的文件系统、网络和其他资源。 Docker 容器被设计为可移植且易于部署。它们可以在任何安装了 Docker 的机器上运行,无论底层操作系统或硬件如何。容器为运行应用程序提供了一致的环境,使得在不同环境(例如开发、测试和生产)之间移动应用程序更加舒适。 Docker 容器可以借助 Docker CLI 或 Docker Compose 或 Kubernetes 等 Docker 工具进行管理。它们可以根据需要启动、停止、暂停和重新启动。还可以使用一系列工具和平台对其进行监控和管理。 总体而言,Docker 容器提供了一种灵活且可扩展的方式来打包和部署应用程序,从而使跨不同环境和平台管理和扩展复杂应用程序变得更加简单。 6.Docker 镜像存储库 Docker 托管着最大的 Docker 存储库之一,称为 Docker 中心。 它是一个Docker镜像的存储和分发系统。它为开发人员和组织提供了一个中央存储库来共享和分发其 Docker 映像,从而使使用 Docker 构建、共享和部署应用程序变得更加愉快。 Docker Hub 允许用户和组织存储和管理其 Docker 映像,并提供版本控制、标记和协作等功能。用户可以从 Docker Hub 搜索和下载镜像,也可以将自己的镜像发布到注册中心。 除了公共注册表之外,Docker Hub 还为想要管理自己的 Docker 映像并确保它们只能由授权用户访问的组织提供私有注册表。 7.InterSystems Docker 镜像存储库 通过使用 Docker Hub 搜索功能,我们可以在 Docker hub 上找到 InterSystems 镜像。 8.安装Docker 为了使用 Docker,我们需要在我们的系统上安装它。 Docker提供了各种操作系统的安装包,包括Windows、macOS和Linux。导航到Docker 网站。 我们可以从Docker网站下载安装包,运行安装程序,按照提示完成安装。 安装Docker Desktop后,我们可以使用Docker CLI(命令行界面)来管理Docker镜像、容器、网络和其他资源。 9.Docker基本命令 这里我们将回顾一些 docker CLI 的基本命令。 (在使用下面详述的命令之前,请确保运行 Docker Desktop) 9.1 列出图像(本地)我们可以使用-----100----- 命令列出系统上可用的所有 Docker 镜像。以下是如何使用此命令: docker image ls 如您所见,目前我们本地没有任何图像9.2 从 Docker 存储库中拉取镜像 我们可以使用-----101----- 命令从注册表下载Docker镜像 docker pull <image> 让我们从 docker hub 中提取 intersystemsdc/iris-community 镜像此时我们应该使用list命令来查看本地的图片做得好! iris-community镜像拉取成功 9.3 本地删除镜像我们可以使用-----102-----命令从我们的系统中删除镜像 docker image rm <image name> 9.4 列出所有现有容器(正在运行和未运行)我们可以使用-----103-----命令来列出正在运行的容器 docker ps 如图所示,此时没有容器在运行。 9.5 创建并启动容器我们可以使用-----104-----命令来创建并启动容器 docker run <image id/namge> 让我们从 iris-community 镜像创建并启动容器这里 -----105----- 或 -----106----- 的含义如下:在后台运行命令并将控制权返回给终端。 是时候再次列出正在运行的容器了 docker ps 我们可以看到我们的 iris-community 镜像容器现在正在运行。 9.6 停止特定容器我们可以使用-----107-----命令来停止正在运行的容器 docker stop <container id/name> 9.7 启动特定容器我们可以使用-----108----- 命令来启动Docker中之前停止的容器。 docker start <container id/name> 9.8 重启特定容器 我们可以使用-----109-----命令来停止和启动Docker中正在运行的容器 docker restart <container id/name> 9.9 删除特定容器 我们可以使用-----110-----命令来删除停止的容器 docker rm <container id/name> 9.10 在正在运行的容器内运行命令我们可以使用-----111----- 命令在正在运行的容器内运行命令。在执行管理任务或出于调试目的时,它可以派上用场。 docker exec -it my-container sh -----111----- 命令的一些常见选项是: -----113----- 或 -----114-----: 即使未连接,此命令也会使 STDIN 保持打开状态,从而允许您与容器交互。 -----115----- 或 -----116-----: 此命令为命令分配一个伪 TTY,允许您在容器内使用终端命令。 -----105----- 或 -----118-----: 这在后台运行命令并将控制权返回到终端。 10.使用docker运行IRIS社区版 使用下面列出的命令通过使用 iris-community 映像来运行容器 docker run -d -p 52773:52773 intersystemsdc/iris-community -----111----- 命令的一些常见选项如下: -----105----- :该命令用于以分离模式启动一个新的 Docker 容器,这意味着该容器将在后台运行,我们可以继续使用终端执行其他任务。 -----121-----: 这个命令帮助我们将容器的端口发布到主机,以便可以从Docker网络外部访问容器。 在下图中,您可以看到 IRIS 在 Docker 中运行。 11.Docker 桌面 GUI Docker 桌面还具有 GUI,我们可以在其中以图形方式使用所有上述命令。 概括 Docker 是一个功能强大的工具,允许开发人员和 IT 团队在容器化环境中创建、部署和运行应用程序。通过提供可移植性、一致性、可扩展性、资源效率和安全性,Docker 可以轻松地跨不同环境和基础设施部署应用程序。随着容器化的日益普及,Docker正在成为现代软件开发和部署的重要工具。在接下来的文章中,我们将学习如何使用 Docker 文件(用于构建 Docker 镜像)、Docker compose(一个 YAML 文件,指定应用程序中每个容器的配置选项)和 Docker 卷(一种持久数据存储机制)用于在 Docker 容器和主机之间共享数据。) 感谢您的阅读! 希望IRIS可以模仿Docker对页面做一下暗黑处理👀 啥意思? 浏览器可以设置暗黑呀
文章
Qiao Peng · 十月 7, 2023

国际卫生信息互操作标准发展简史

卫生信息和其它信息化一样,经历了数码化、数字化到当今的数字化转型,卫生信息互操作一直伴随左右。 数码化(digitization):国内90年代开始,HIS全面铺开,卫生信息进入数码化时代。数码化初期业务集中在HIS上,互操作需求不高,点对点接口可以满足绝大多数需求。 数字化(digitalization):在2000年之后,各种专科系统、尤其是电子病历的诞生,医保和新农合的实施,要求卫生信息共享交换,以提高流程自动化水平。互操作需求爆发,2007年集成平台开始进入市场,卫生信息化进入数字化时代。 数字化转型(digital transformation):2014年,国内正式进入移动互联网时代;次年《全国医疗卫生服务体系规划纲要(2015—2020年)》发布,卫生信息化的服务对象(服务于医护技到服务于患者)和业务形态(临床管理到患者服务)都发生了翻天覆地的变化,开始步入数字化转型的时代。它对互操作提出了更高的要求 - 利用互操作,增强全员参与,为卫生服务创造新价值、发展新业务,推动医疗机构持续数字化转型。 可以说,卫生信息互操作在整个的卫生信息产业中愈发重要。 国际卫生信息互操作发展了30年,国内也发展了20年,但卫生信息互操作依然是一个挑战。 知史而明鉴,识古而知今。我们看看国际卫生信息互操作发展的历程,对未来的卫生信息互操作有什么借鉴。 卫生信息互操作标准的要素 HIMSS把信息互操作/集成定为4个不同的级别: 基础级别,仅仅打通了系统间进行数据通讯的通道; 结构级别,在基础级别上,定义了数据交换的格式和语法; 语义级别,建立在行业通用的基础模型和数据编码上,使用标准化的行业语义来定义数据元素,使用标准的值集。因此语义级别的互操作是全行业可以理解并有确定行业意义的互操作级别。或者说语义级别的互操做才是基于标准的互操作。 组织级别,通常都是由国家、行业协和和行业标准开发组织开发的。它加入了政策、社区、法律等方面的考虑,分析了通用的业务流程和工作流,在此基础上设定了参与互操作各方的角色、权限,服务和知情同意策略等。我们的互联互通,就是组织级别的互操作。 目前的卫生信息互操作项目多数停留在结构级别。只有达到语义级别的信息互操作/集成,才是标准化的信息互操作/集成,才能降低实施成本和提高实施效率。 做到语义级别的互操作标准并不容易,首先是消除语义歧义、其次行业普遍认可、再次是要覆盖行业用例并具有适应行业不断变化需求的弹性。 图片来源:EuroVulcan Conference 2023 先说消除语义歧义。要在信息交换时消除语义歧义,需要在语言、语法、词义、句法等多方面努力,而且涉及到数据的颗粒度。尤其在医疗行业,完整、消除歧义才能保障卫生信息准确和医疗行为安全! HIMSS认为要消除语义歧义、达到语义级互操作性,需要基于五位一体的语义标准,包含: 词汇/术语标准:依靠结构化的词汇、术语、代码集和分类系统来表示健康概念。例如ICD-10、SNOMED-CT、LOINC、 RxNorm是行业里典型的词汇和术语标准。 内容标准:描述信息交换中,数据内容的结构和组织。而HL7 CDA、HL7 V2、C-CDA都是行业内容标准。 传输标准:定义了计算机系统、文档架构、临床模板、用户界面和患者数据链接之间交换的消息格式和传输方式。传输方式确定了卫生信息交换的“推”和“拉”方式。DICOM、IHE等都是传输标准。 隐私和安全标准:是确定谁、何时、出于何种目的、使用哪种个人健康信息的权利,以及如何护健康信息的机密性、可用性和完整性的标准。美国的HIPAA和欧洲的GDPR都是关于隐私和安全的标准。 标识符标准: 是用来唯一标识患者、机构、医护技、设备等实体的方法。例如咱们互联互通里用到的OID和美国的护士标识NCSBN ID … 并非消除了语义歧义的标准就能被广泛接受和认可,需要行业标准化组织的推动,实现厂商中立,毕竟互相竞争的厂商很难接受对方的企业标准。回顾一下行业里流行的标准,无论是术语标准、还是消息和文档标准,都是行业里标准化组织发布的,其中最有名的就是HL7。 从这个行业标准发展史可以看到,毫无例外的,标准先从术语标准开始,例如ICD、SNOMED,历史都非常久远。而我们常用的HL7 V2有30多年历史了,CDA和V3也20年左右了。从2014年,HL7推出了FHIR。这些标准是为何以及如何演进的? 互操作标准发展要满足不断变化的行业需求和用例 先看看90年代初的互操作的业务环境,就像下图那么简单:医疗机构还处在数码化向数字化转换的时代 - HIS等业务系统开始大规模部署以实现流程和数据的数码化,同时产生了非常有限的跨业务系统的流程自动化 – 信息集成需求。实时卫生信息交换的需求基本都在医疗机构内部(局域网,那时候WWW刚诞生),而院内的业务系统数量非常有限、且系统边界清晰,使用的用户基本就是医护技和管理人员,需要的互操作流量规模可以准确预测。而且系统互操作的技术手段非常有限,基本就是文件传输、串并口、socket,而SOAP(2000年)、RESTful(2000年)、甚至HTTP(1996年)等协议都还没有产生。 HL7 V2 这就是HL7 V2消息交换标准产生的时代,和所面临的互操作业务需求:它将业务事件和业务事件的上下文封装在消息结构中,在系统边界中传递这些消息。 业务系统边界清晰,一般用消息引擎来路由和转发这些消息,从而不打破系统边界。各个业务系统只要能接收/发送并处理这些标准化的消息即可。 近距离看一个HL7 V2消息示例,它是一个由多种分隔符分割的字符串,由区段和字段构成:区段是一组分类的数据,例如PID是患者信息区段;而字段是每个数据项,例如患者标识(在PID区段里)是“1182594^^^系联医院&1^^系联医院&1”,它本身也是一个结构,用于放标识符(1182594)和标识分配机构(系联医院)等信息。 而事件就是消息头区段里的ORM^O01,其中ORM代表业务域”通用医嘱消息”,O01代表事件“医嘱请求”。 消息头区段 MSH|^~\&|HIS|系联医院|系联实验室|系联医院|202302160002||ORM^O01|demo22903||2.5|382|||||UTF8 患者区段 PID||1182594^^^系联医院&1^^系联医院&1|||李小明||19570320|M|||北京市朝阳区建国门外大街乙12号2702 就诊区段 PV1|22903|O|心内科||||35030099^唐^南|||MED|||||||35019964^郑^顾樽||22903|||||||||||||||||||||||||202302160002^M 保险区段 IN1|1|65110116^城镇职工医保| 医嘱区段 ORC|NW|MS:1182594:1|||SC||||202302160002^M||||||||||||||||||||LAB 医嘱明细区段 OBR|1|MS:1182594:1||4548-4^糖化血红蛋白^loinc 为什么HL7 V2会是这种难读的格式?因为它是窄带时代的产物,当时通讯带宽有限,数据格式需要紧凑,通常仅用分隔符分割,以减少传输的数据量(相较与XML,通常能减少80%以上的数据),如今在一些检验检查设备的通讯协议中还能看到类似的设计。同时,从早期直到现在,多数HL7 V2消息是通过socket交换的。这些特征都是90年代互操作的历史印记。 HL7 V2是按模式复用的角度设计的颗粒度,也就是说它的颗粒度是信息区段。但并不是所有的信息区段都有独立的含义和复用的价值,例如区段TQ1、TQ2定义服药时间和用药途径,没有单独存在的可能和直接复用的价值。 另外,V2消息的字段随意性很大,相同内容可以放在不同的字段甚至区段里面;用户还被鼓励创建自定义的Z区段进行消息体扩展。也就是说它标准化程度不高,需要实施的双方事先约定好数据具体怎么放才能实现信息交换。同时V2术语约束机制很弱。 HL7 V3 和 CDA 世纪之交,卫生信息化发展提速,电子病历和各种专科系统崛起,更极大推动了卫生信息的交换和流程自动化的需求,同时对交换的语义标准化程度有了更高的要求。这需要更严谨的互操作业务抽象和术语约束。卫生信息正式进入数字化时代,也正是在这一时期,诞生了包括IHE、CDA、HL7 V3在内的众多互操作标准。 从模型抽象的角度看,应该全面包含用例模型、信息模型和交互模型,但V2的关注点基本在交互层面,对其它层面的抽象很弱。 由此,携着其著名的参考信息模型(RIM)方法论,V3在2005年横空出世,对业务场景进行分析,抽象交互逻辑,从参考信息模型到领域信息模型,再到精细化消息信息模型,最终产生需要的消息模型。模型以XML进行序列化,相较于V2,进步了许多。 这套方法论产生的V3消息标准化程度很高。但为了覆盖所有业务需求,RIM是高度抽象的(难于理解的);同时V3方法论是“按约束设计”(design by constraint),试图涵盖所有应用场景,避免自定义扩展,这使其越来越复杂、越来越庞大,而且用户没有RIM基础很难自己对其扩展,从一个极端走向另一个极端。 V3的高复杂性和高使用门槛,造成了它事实上的失败,没有成为V2的替代者,就像一些专家评论的 – “RIM创建了语义互操作性,但没有创建临床互操作性“。 注意,国内有一些实践中,甚至没有严格遵循V3发布的XML schema,直接用代码拼出XML字符串,也不做消息校验,这不算标准的V3。 同样在世纪之交,很多业务需要即时性不那么强、但数据更完整的交换 - 小结性质的临床文档交换。在这个领域,最主流的是CDA临床文档架构标准。CDA源于 1996 年就开始的临床文档中结构化标记工作,并在1997年并入HL7,随后使用V3参考信息模型来完善和发展。大家可能注意到前面的图上CDA早于V3发布,就是这个原因。 CDA临床文档架构,用于描述结构化文档,同时允许插入供人类解读的非结构化部分。它产生的文档具有上下文完整、可持久保存、可管理、可认证等特性。CDA文档和衍生的CCD文档广泛用于医疗机构边界间和医疗系统边界间的文档交换,或作为具有法律效力的临床文档依据保存在文档仓库。 CDA是成功的,可能是V3基础上唯一成功的部分,但它不能解决细数据颗粒度访问的需求。 IHE 虽然RIM基于业务场景、角色、触发事件等分析,但它的交付物 – 消息模型并无法执行流程与角色的约束。 服务用于业务场景里流程、角色的表达,功能内聚,可以通过企业服务总线(ESB)来协同,比消息路由规则更直观、更灵活,更适合实现业务流程的自动化。通常服务是比较大尺度的业务表达,服务标准广泛采纳的难度在于它实际上是规范业务流程和业务方法,而实际上多数机构的业务并不那么一致。 IHE(Integrating the Healthcare Enterprise)是国际上比较流行而成功的卫生信息交换服务规范。它是1998年,由HIMSS 和RSNA(北美放射学协会)发起,由一帮放射学和IT技术专家创建的。它最初为放射影像信息共享提供技术框架,以解决即便有了DICOM后在不同厂商系统间放射影像信息交换的标准和流程上的困难,后面逐步涵盖了越来越多的业务场景。IHE使用已经发布的卫生信息内容标准和术语标准,例如DICOM、HL7、LOINC等,来构建自己的服务框架,利用企业服务总线来协同这些服务,可以实现比消息交互更功能内聚的互操作架构: • 服务本身封装了事件、上下文 • 服务针对于场景定义了流程和角色 • 适合跨清晰的业务系统边界间信息交换 • 服务有多种互操作模式: • Web 服务本身是可互操作的,这意味着任何客户端都可以直接调用 Web 服务 • 服务可以通过企业服务总线(ESB)来协同,比消息路由规则更直观、更灵活 IHE分析每个业务场景(Profile),将业务场景中参与方定义为角色(Actors),场景中角色的交互定义为事务(Transactions)。例如跨机构的文档共享业务场景中,有4个不同的角色:文档源、文档注册器、文档使用者和文档仓库。而交互事务有注册、查询、获取等 IHE能在服务标准上取得成功,在于它先在参与的用户基础上规范业务,然后再基于规范的业务发布相应的服务,也就是说,使用IHE需要先认同它的规范出的业务。 IHE一直随着业务、技术和互操作标准的发展而不断演进,从最初使用DICOM + HL7 V2,到最新基于FHIR;从最初的影像信息交换到最近的患者穿戴设备的数据交换。例如在2007年,IHE创建了基于HL7 V3的跨机构档案共享的Profile – XDS.b,之后又推出了基于FHIR的诸多移动端服务。 卫生信息数字化转型对卫生信息互操作标准的挑战 2014年是分水岭的一年:两年前的2012年,IBM提出数字化转型概念; 2014年,移动互联网用户超过了桌面互联网用户,正式进入移动互联网时代;次年中国政府工作报告中首次提出“互联网+”行动计划。 图片来源:HL7 国际 在卫生信息领域, 2013年HL7绘制了这张图,总结10年前发生在我们身边的重大技术革命、生物信息学进步和社会经济变革和对卫生信息互操作的挑战。 而2013年之后的最近10年,我们面临了更多的机会与挑战 – 云计算、虚拟现实、5G、AIGC、医保异地实时结算、新冠全球大流行… 简单梳理一下在数字化转型时代下,卫生信息行业面临的挑战: 挑战1 – 卫生信息互操作的范围扩大 移动互联网时代的患者服务,突破了卫生信息化的传统边界:不再是院内的、不再仅是临床数据、不再局限于医护技用户。一旦面向患者服务,传统的互操作模式和架构 - 消息引擎、企业服务总线都受限于其有限的业务弹性,难于满足患者服务的需求。其性能压力挑战就像12306面临的一样,而卫生信息却远比火车订票信息复杂的多。 卫生信息互操作需要数据覆盖面更广、更灵活扩展、更具有弹性的标准。 挑战2 – 快速的业务迭代进化 传统信息化系统建设是这种烟囱模式 – 从底层数据模型建模、业务逻辑开发到用户界面展现,自成一体,对语义标准化和互操作要求不高,同时数据和逻辑在非标准语义的情况下也难于复用。 数码化时代和数字化时代,面向医护技的应用可以3-5年升级换代一次,烟囱模式问题不大。但数字化转型时代,面向患者服务的应用可能每周都在升级! 这种烟囱模式不仅应用开发周期长,还阻碍了我们利用已有建设成果快速进化业务的能力,显然满足不了当今业务快速迭代进化的需要。 我们不能从零开始,需要统一语义、统一服务,复用已有的数据和服务/API,并基于此快速构建和迭代新的业务 – 这正是数字化转型的核心。信息互操作不仅解决数字化问题,也是解决数字化转型的钥匙。这当然也需要更广泛统一语义的互操作标准。 挑战3 – 基于知识库和机器学习的自动临床决策 生物信息学进步让我们认识到更底层致病和治病的机理,获得了更多类型的决策数据;信息化建设让我们积累了大量的数据;医疗物联网带来了更多的院外数据。但人类大脑决策受制于能同时处理的5-10个信息量的极限,难于在医疗决策过程中利用爆炸的数据规模和数据类型。 信息来源:Evidence-Based Medicine and the Changing Nature of Healthcare: 2007 IOM Annual Meeting Summary 知识库和机器学习的进步可以帮助医生突破人工认知能力的限制,并提供更智能的服务。但同时这对行业语义和语义的颗粒度提出了挑战 – 知识库和机器学习都需要结构化、干净、完整、无歧义的数据,而决策逻辑和质量指标更需要细颗粒度的行业统一的语义,以避免医疗差错、实现大范围和低成本的复用。例如,由于行业整体缺乏统一语义,当前的数据中心项目建设中的80%的工作都用于源数据分析、处理,成本巨大、数据的准确率和实效性仍很差。 将这些辅助决策集成到医生的诊疗流程是另一个面临的挑战,对决策的实时性要求很高,需要集成方式上的创新。 挑战4 – 数据资产要素流通 2022年底国家提出了数据资产要素流通的指导意见 -《中共中央 国务院关于构建数据基础制度更好发挥数据要素作用的意见》,而各地的大数据局也应运而生。 当数据继人、财、物、知识产权之后,成为资产要素并且可以流通时,它显然能创造更大的价值。而卫生信息作为非常重要且高度敏感的数据资产,如何安全、可靠地流通?我们面临着建设广泛认同和开放的行业语义模型、可追溯数据来源、提供多样化数据资产流通形态等挑战。 与其它资产要素不同,数据资产需要更复杂的确权 – 数据到底是谁产生的?谁具有有拥有权、加工权和使用权? 这些需要可追溯数据来源。 而不同于知识产权,数据资产不能一给了之,否则很难保证数据不被盗用与滥用。这需要对数据资产提供多样化的流通形态 – 除了原始数据和简单脱敏后数据、还应该有加工后的数据、甚至产生的数据模型,例如特定人群的特定疾病风险,对于保险公司是非常重要的数据,这样的数据大可不必以原始数据的形态流通给保险公司,而是基于机器学习,在原始数据集上运算和学习,并返回简单结果或预测模型给保险公司。 卫生信息互操作的新篇章 – FHIR 在2014年,为应对各种时代挑战,同时正视V3的失败,HL7发布了其新一代的卫生信息互操作标准FHIR (Fast Healthcare Interoperability Resources)。 发布近10年后,FHIR如今已经接近HL7 V2的采纳度。我们看到越来越多国家采纳FHIR标准,甚至作为卫生信息数字化转型的支柱,例如英国的NHS和最近印度尼西亚的SATUSEHAT国家卫生信息交换平台采用FHIR作为基础。 从名字看,FHIR只是HL7发布的新一代卫生信息互操作标准,它如何应对这么多挑战,甚至成为卫生信息数字化转型的支柱? HL7总结了之前发布的互操作标准,使FHIR主要在这些方面做了创新: • 更细颗粒度和更广业务范围的行业语义 • 更灵活的语义扩展和再约束机制 • 成熟度机制,标准随大范围使用与反馈获得版本升级和成熟 • 更多样的互操作模式 • 更广泛的生态建设, 利用生态解决远远超过单一互操作标准能解决的问题 FHIR采用的CC0许可协议,也叫“知识共享”,是指知识产权“不保留任何权利”。这与之前的标准不同,促进了FHIR生态的高速建设。而丰满的FHIR生态是其能在持续应对各种挑战时保持韧性的保障。 我们仔细看看FHIR是如何应对挑战的 应对卫生信息互操作的范围扩大 FHIR不试图通过发布的标准资源去涵盖所有业务需求,而是按80/20原则将最常用的那80%的用例总结归纳为数量最少的资源模型,降低了采纳门槛。对剩下的20%不常见的用例,它提供了一套标准的扩展/再约束机制(profile、implementation guide),并且这套机制产生的扩展/再约束是计算机可读(可理解)的,从而保障了更高的适用度。 FHIR的资源模型是对象模型,通过资源引用表达模型间的关系,避免明文上下文的传递所带来的潜在数据冗余与冲突。 FHIR的资源模型是细颗粒度的,并且业务覆盖范围广。 FHIR的成熟度模型,保证了FHIR资源和其它工件是随大范围使用与反馈获得版本升级和成熟。同时让用户明确知道资源的成熟度,并自己决定是否采用。 支持多种互操作模式,尤其是RESTful API,可以实现对资源细颗粒度操作,避免发布“大服务”,而是让用户自己组合“微服务” – API。也因此,FHIR可以摆脱消息引擎、ESB等中心化、缺乏弹性扩展能力的组件,再加上数据的JSON序列化方式,满足移动互联网时代的互操作所需的轻量化和弹性。 FHIR API: 实例层面的交互 read 读取资源的当前状态 vread 读取资源的特定版本状态 update 使用id更新已存在的资源 patch post一系列更改到已存在的资源 delete 删除资源 history 获取特定资源的修改历史 类型层面的交互 create 用服务器指定的id创建新资源 search 基于过滤条件查询资源类型 history 获取特定资源类型的修改历史 整个系统的交互 capabilities 获取系统的能力声明 batch/transaction 在单一交互中更新、创建或删除一系列资源 history 获取所有资源的修改历史 search 基于过滤条件跨资源类型查询 应对快速的业务迭代进化 FHIR是怎么应对快速的业务迭代进化需求的?先看看SMART on FHIR这个应用架构: 在注意到电子健康档案系统(EHR)架构不灵活、并影响到应用快速创新后,2010年哈佛医学院和波士顿儿童医院发起了名为SMART(Substitutable Medical Applications and Reusable Technologies, 可替代的医学应用和可复用技术) 的项目,目的是构建一个应用平台架构,让医学应用可以在上面被开发,并无需修改地运行在跨不同的医疗IT系统间 – 就像苹果和安卓移动应用那样即插即用。但与移动应用不同的是,在医疗应用架构上采用通用、可互操作的数据规范将是这一生态系统的关键要求。 为实现这个目标,它开发了一套绑定术语的、用RDF表达的标准临床数据模型 - SMART Classic、基于RESTful API开发了一套操作数据的标准API、使用Oauth2作为标准认证机制、HTML5作为标准界面开发技术。理想很好,但当时并没有多少厂商愿意支持,因为它并不是业内广泛采纳的互操作标准,SMART Classic仅覆盖临床,且RDF并不流行。 SMART早期就参与了FHIR前身 - Fresh Look Task Force的讨论。在2013年,SMART注意到尚未发布的FHIR可以帮助解决标准数据模型、标准API等,且数据模型的覆盖面更广,于是在FHIR发布的2014年,迅速成为FHIR生态的一员,变成了SMART on FHIR。 如今,SMART 应用市场已经有大量免费或收费的应用,例如心血管病风险评估、新生儿胆红素管理、患者360视图... 作为用户,你喜欢的应用下载部署在相应版本要求的FHIR服务器上即可,实现了即插即用。作为SMART应用的开发者,只需要专注于业务本身,无需设计底层数据模型和API,加速了应用开发,从而支撑业务快速迭代进化。 SMART on FHIR只是基于FHIR加速应用创新的一个例子, Apple Health也是基于FHIR的,而Epic的用法也极具启发性:Epic在2022年底将其应用市场升级为Connection Hub,通过开放55个FHIR资源和相应API,让开发者基于FHIR标准开发应用且这些应用都可以运行在所有Epic 电子病历/电子健康档案系统上。短短不到一年,已经有众多开发商和开发者贡献了近500个不同类型的应用,而Epic借助FHIR和FHIR生态,快速构建了自己的应用生态并让其用户直接受益 ,也让它的电子病历/电子健康档案系统成为一个应用平台! 应对基于知识库和机器学习的自动临床决策 无论是基于知识库规则还是基于机器学习的决策,统一语义和数据的可访问性都是关键。FHIR首先为决策提供了统一语义和对细颗粒度的决策数据的简单访问API。而有了统一语义的细颗粒度数据模型和标准API,生态里应该很容易设计出决策支持架构。 的确如此,SMART团队在2015年启动了CDS Hooks项目,基于FHIR构建了决策服务和决策架构,从而让决策自动集成到业务流程之中,并提供多种决策方式 – 信息提示、建议和启动SMART 应用。它的功能示意如下: 而FHIR也在R5中扩展了自己的互操作范式,增加了订阅模式,使决策系统和业务流程可以基于发布/订阅的松耦合架构进行集成。 应对数据资产要素流通(数据20条) 2020年12月,发改委、网信办等四部门发布《关于加快构建全国一体化大数据中心协同创新体系的指导意见》,提出要完善覆盖原始数据、脱敏处理数据、模型化数据和人工智能化数据等不同数据开发层级的新型大数据综合交易机制,确定了数据交易的四种产品形态。 FHIR的统一语义首先保证了卫生信息数据资产的统一可量化,基于FHIR的profile可以进一步基于不同用途对数据质量进行约束检查和过滤。 另一方面,FHIR提供多形态的数据流通与利用能力,包括 • 对细颗粒度的数据操作 – FHIR API (CRUD) • 对数据的共享和交换 • FHIR API、服务 • FHIR 消息、文档 • 对大规模数据检索 – 基于查询参数的FHIR API • 例如: GET http://fhirsvr.com/Observation?code-value-quantity=loinc|1234-1$lt9.2 • 对大规模的数据分析 • 临床质量指标与决策支持 – CQL on FHIR • 数据分析 – SQL on FHIR • 科研数据分析 – OMOP on FHIR 这其中FHIR生态下的几个项目尤其令人侧目: CQL(Clinical Quality Language, 临床质量语言) 也是HL7组织下的一个标准, 用于定义和编写质量指标逻辑 - 那些用于决策的指标。起初它以QDM(Quality Data Model)作为基础数据模型,其语法“隐藏”了大部分定义质量指标逻辑的复杂性从而简化实施。而今它利用FHIR,从而让质量指标逻辑更易于阅读和理解、执行更加便捷,并融入到大的FHIR生态。 使用CQL,可以设计需要的数据逻辑,让CQL引擎在后台利用FHIR数据进行运算,而客户端只拿到结果,让数据可用而不可见。 不止如此,另一个生态项目SQL on FHIR的初衷是让统计分析和机器学习的客户端可以使用SQL无缝地访问FHIR数据。作为最成熟和通用的数据操作方式,SQL也可以让FHIR生态和隐私计算生态打通,实现更多样的“数据可用不可见”。 这里总结了FHIR特点与数据资产要素流通要求之间的匹配: 从国际卫生信息互操作标准发展史、对当今各种互操作挑战的应对、以及它日益成熟壮大的生态,能看到FHIR强大的生命力和广泛的适应性。随着FHIR的成熟,FHIR的生态正快速填补卫生信息蓝图的每一块拼图。 相信借鉴FHIR和其生态,我们也能设计和实现更好的卫生信息互操作。
文章
姚 鑫 · 二月 20, 2021

第四十二章 Caché 变量大全 $ZTIMESTAMP 变量

# 第四十二章 Caché 变量大全 $ZTIMESTAMP 变量 包含协调世界时间格式的当前日期和时间。 # 大纲 ```java $ZTIMESTAMP $ZTS ``` # 描述 `$ZTIMESTAMP`包含协调的通用时间值形式的当前日期和时间。这是世界范围内的时间和日期标准;此值很可能与当地的时间(和日期)值不同。 `$ZTIMESTAMP`将日期和时间表示为以下格式的字符串: ```java ddddd,sssss.fff ``` 其中`ddddd`是一个整数,指定自1840年12月31日起的天数;`sssss`是一个整数,指定自当天午夜以来的秒数,`fff`是一个可变的数字,指定小数秒。这种格式类似于`$HOROLOG`,只是`$HOROLOG`不包含分数秒。 假设当前日期和时间(世界协调时)如下: ```java 2018-02-22 15:17:27.984 ``` 当时,`$ZTIMESTAMP`的值为: ```java 64701,55047.984 ``` `$ZTIMESTAMP`报告协调世界时(UTC),它独立于时区。因此,`$ZTIMESTAMP`提供了一个跨时区的统一时间戳。这可能不同于本地时间值和本地日期值。 `$ZTIMESTAMP`时间值是一个十进制数值,以秒及其分数为单位计算时间。分数秒的位数可能从零到九不等,具体取决于计算机时钟的精度。在视窗系统上,小数精度是三位小数;在UNIX系统上,它是六位十进制数字。`$ZTIMESTAMP`在此小数部分中抑制尾随零或尾随小数点。请注意,在午夜后的第一秒内,秒表示为`0.fff`(例如,`0.123`);这个数字不是ObjectScript规范形式(例如,`. 123`),这会影响这些值的字符串排序顺序。在执行排序操作之前,您可以添加一个加号(`+`)来强制将数字转换为规范形式。 比较了返回当前日期和时间的各种方法,如下所示:。 - `$ZTIMESTAMP`包含以系统间IRIS存储(`$HOROLOG`)格式表示的UTC日期和时间(小数秒)。小数秒以三位精度(在Windows系统上)或六位精度(在UNIX®系统上)表示。 - `$NOW`返回当前进程的本地日期和时间;不应用本地时间变体(如夏令时)。不带参数值的`$NOW`根据`$ZTIMEZONE`特殊变量的值确定当地时区。带有参数值的`$NOW`返回与指定时区参数对应的时间和日期。`$NOW(0)`返回UTC日期和时间。忽略`$ZTIMEZONE`的值。`$now`返回InterSystems IRIS存储(`$HOROLOG`)格式的日期和时间。它包括小数秒;小数位数是当前操作系统支持的最大精度。因此,`$NOW(0)`返回的UTC时间可能比`$ZTIMESTAMP`返回的秒精度高 - `$HOROLOG`包含采用InterSystems IRIS存储格式的本地变量调整日期和时间。它不记录小数秒。`$HOROLOG`如何解析小数秒取决于操作系统平台:在Windows上,它将任何小数秒四舍五入到下一整秒。在UNIX®上,它会截断小数部分。 注意:比较当地时间和UTC时间时要谨慎: - 将UTC时间转换为本地时间的首选方法是使用`$ZDATETIMEH(UTC,-3)`函数。此函数根据当地时间变量进行调整。 - 不能通过简单地添加或减去`$ZTIMEZONE*60`的值来转换本地时间和UTC时间。这是因为,在许多情况下,当地时间会根据当地时间的变化进行调整(例如夏令时,它会将当地时间季节性地调整一小时)。这些本地时间变量不会反映在`$ZTIMEZONE`中。 - UTC时间是使用格林威治子午线上的时区计数来计算的。这和格林威治当地时间不一样。术语格林威治标准时间(GMT)可能会令人混淆;格林威治当地时间在冬季与UTC相同;在夏季,它与UTC相差一个小时。这是因为采用了当地时间变量,即英国夏令时(British Summer Time)。 - 时区与UTC和本地时间的偏差(例如季节转换为夏令时)都会影响日期和时间。从本地时间转换为UTC时间(反之亦然)可能会更改日期和时间。 不能使用`SET`命令修改此特殊变量。尝试这样做会导致``错误。 # 协调世界时转换 可以使用带有tFormat值7或8的`$ZDATETIME`和`$ZDATETIMEH`函数将本地时间信息表示为协调世界时(UTC),如下例所示: ```java /// d ##class(PHA.TEST.SpecialVariables).ZTIMESTAMP() ClassMethod ZTIMESTAMP() { WRITE !,$ZDATETIME($ZTIMESTAMP,1,1,2) WRITE !,$ZDATETIME($HOROLOG,1,7,2) WRITE !,$ZDATETIME($HOROLOG,1,8,2) WRITE !,$ZDATETIME($NOW(),1,7,2) WRITE !,$ZDATETIME($NOW(),1,8,2) } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZTIMESTAMP() 02/10/2021 09:46:32.53 02/10/2021T09:46:32.00Z 02/10/2021T09:46Z 02/10/2021T09:46:32.53Z 02/10/2021T09:46Z ``` 上面的`$ZDATETIME`函数都以协调世界时(而不是本地时间)的形式返回当前时间。这些从本地时间转换的时间值可能不同,因为`$Now`不会针对本地时间变量进行调整;`$ZTIMESTAMP`和`$HOROLOG`会针对本地时间变量进行调整,并可能在必要时相应地调整日期。`$ZTIMESTAMP`显示值与tFormat 7或8转换后的显示值不同。Tformat值7和8在时间值之前插入字母`“T”`,在时间值之后插入字母`“Z”`。此外,因为`$HOROLOG` TIME不包含小数秒,所以上例中精度为2的小数位用零填充。 通过使用以下语法形式之一调用`Timestamp()`类方法,可以获得与`$ZTIMESTAMP`相同的时间戳信息: ```java DHC-APP> WRITE !,$SYSTEM.SYS.TimeStamp() 65785,35395.629 DHC-APP> WRITE !,##class(%SYSTEM.SYS).TimeStamp() 65785,35408.245 ``` # 示例 下面的示例将`$ZTIMESTAMP`的值转换为本地时间,并将其与本地时间的两种表示形式进行比较:`$NOW()`和`$HOROLOG`: ```java /// d ##class(PHA.TEST.SpecialVariables).ZTIMESTAMP1() ClassMethod ZTIMESTAMP1() { SET stamp=$ZTIMESTAMP,clock=$HOROLOG,miliclock=$NOW() WRITE !,"当地日期和时间: ",$ZDATETIME(clock,1,1,2) WRITE !,"当地日期和时间: ",$ZDATETIME(miliclock,1,1,2) WRITE !,"UTC日期和时间: ",$ZDATETIME(stamp,1,1,2) IF $PIECE(stamp,",",2) = $PIECE(clock,",",2) { WRITE !,"当地时间为UTC时间" } ELSEIF $PIECE(stamp,",") '= $PIECE(clock,",") { WRITE !,"时差影响日期" } ELSE { SET localutc=$ZDATETIMEH(stamp,-3) WRITE !,"UTC转换为本地: ",$ZDATETIME(localutc,1,1,2) } QUIT } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZTIMESTAMP1() 当地日期和时间: 02/10/2021 17:54:46.00 当地日期和时间: 02/10/2021 17:54:46.93 UTC日期和时间: 02/10/2021 09:54:46.93 UTC转换为本地: 02/10/2021 17:54:46.93 ``` 下面的示例比较了`$ZTIMESTAMP`和`$HOROLOG`返回的值,并显示了如何转换`$ZTIMESTAMP`的时间部分。(请注意,在此简单示例中,只针对本地时间变化(如夏令时)进行了一次调整。其他类型的局部变化可能会导致时钟秒和戳秒包含不可调和的值。) ```java /// d ##class(PHA.TEST.SpecialVariables).ZTIMESTAMP2() ClassMethod ZTIMESTAMP2() { SET stamp=$ZTIMESTAMP,clock=$HOROLOG WRITE !,"当地日期和时间: ",$ZDATETIME(clock,1,1,2) WRITE !,"UTC日期和时间: ",$ZDATETIME(stamp,1,1,2) IF $PIECE(stamp,",") '= $PIECE(clock,",") { WRITE !,"时差影响日期" } SET clocksecs=$EXTRACT(clock,7,11) SET stampsecs=$EXTRACT(stamp,7,11)-($ZTIMEZONE*60) IF clocksecs=stampsecs { WRITE !,"没有本地时间变量" WRITE !,"本地时间是时区时间" } ELSE { SET stampsecs=stampsecs+3600 IF clocksecs=stampsecs { WRITE !,"夏令时变量:" WRITE !,"当地时间与时区时间相差1小时" } ELSE { WRITE !,"由于当地时间不同,无法协调" } } QUIT } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZTIMESTAMP2() 当地日期和时间: 02/10/2021 17:58:16.00 UTC日期和时间: 02/10/2021 09:58:16.85 没有本地时间变量 本地时间是时区时间 ```