Files
contractor/prompt.cfm
T

263 lines
12 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<cfsetting showdebugoutput="no" enablecfoutputonly="true">
<cfheader name="Content-Type" value="application/json; charset=utf-8">
<cfheader name="Access-Control-Allow-Origin" value="*">
<cfif cgi.request_method EQ "OPTIONS"><cfoutput>OK</cfoutput><cfabort></cfif>
<!--- Создать таблицу при первом обращении --->
<cftry>
<cfquery datasource="baza">
DROP TABLE IF EXISTS prompts CASCADE
</cfquery>
<cfquery datasource="baza">
CREATE TABLE IF NOT EXISTS prompts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
role VARCHAR(20) NOT NULL,
name VARCHAR(100) NOT NULL,
body TEXT NOT NULL,
parent_id UUID REFERENCES prompts(id),
is_active BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT now(),
created_by VARCHAR(50) DEFAULT 'system',
notes TEXT
)
</cfquery>
<cfcatch></cfcatch>
</cftry>
<!--- Авто-сид: если таблица пуста — создать дефолтные промпты --->
<cftry>
<cfquery name="cntCheck" datasource="baza">SELECT COUNT(*) as cnt FROM prompts</cfquery>
<cfif cntCheck.cnt EQ 0>
<!--- Дефолтный промпт для extract (первый документ) --->
<cfquery datasource="baza">
INSERT INTO prompts (role, name, body, is_active, notes) VALUES (
'extract', 'default-v1',
'Ты — анализатор договоров облачного провайдера.
Ниже текст спецификации услуг из ПЕРВОГО документа (базовый договор).
Извлеки ВСЕ строки спецификации в JSON-массив.
Верни СТРОГО JSON без пояснений. Не используй markdown-блоки.
ФОРМАТ:
{
"mode": "partial",
"ops": [
{"action": "ADD", "new_row": {"name": "полное наименование", "price": число, "qty": число, "sum": число, "date_start": "YYYY-MM-DD"}, "comment": ""}
]
}
ПРАВИЛА:
1. Извлеки КАЖДУЮ строку таблицы спецификации как отдельную ADD-операцию.
2. Пропускай итоговые строки ("Итого...") и строки с подписями.
3. Если ячейка пустая — ставь null (не пиши 0).
4. price, qty, sum — ЧИСЛА, не строки.
ТЕКСТ ДОКУМЕНТА:
---
{doc_text}
---',
true, 'Авто-создан из llm_prompt.py _build_initial'
)
</cfquery>
<!--- Дефолтный промпт для diff (сравнение с текущей спецификацией) --->
<cfquery datasource="baza">
INSERT INTO prompts (role, name, body, is_active, notes) VALUES (
'diff', 'default-v1',
'Ты — анализатор допсоглашений к договорам облачного провайдера.
У тебя есть текущая спецификация услуг и текст нового допсоглашения (ДС).
Твоя задача — определить, какие изменения вносит ДС в текущую спецификацию.
Верни СТРОГО JSON без пояснений. Не используй markdown-блоки.
ФОРМАТ:
{
"mode": "partial" | "full_replace",
"ops": [
{"action": "ADD", "new_row": {"name": "...", "price": число, "qty": число, "sum": число, "date_start": "YYYY-MM-DD"}, "comment": "..."},
{"action": "UPDATE", "target_id": "rN", "new_values": {"price": число}, "comment": "..."},
{"action": "DELETE", "target_id": "rN", "comment": "..."},
{"action": "UNRESOLVED", "new_values": {"name": "...", "price": число, ...}, "reason": "почему не смог сопоставить"}
]
}
ПРАВИЛА:
1. mode = "full_replace" — если в тексте есть фразы: «в следующей редакции», «заменить приложение», «излагается в следующей редакции». При full_replace — опиши ВСЕ новые строки как ADD.
2. mode = "partial" — если ДС меняет только отдельные строки.
3. Для UPDATE/DELETE — укажи target_id (r1, r2...) из списка текущей спецификации. НЕ придумывай новые id.
4. new_values в UPDATE — только ИЗМЕНЁННЫЕ поля (не все).
5. Если не можешь однозначно сопоставить строку — action: UNRESOLVED с reason.
6. Пропускай итоговые строки и подписи.
7. price, qty, sum — ЧИСЛА (не строки).
ТЕКУЩАЯ СПЕЦИФИКАЦИЯ:
{spec_current}
ТЕКСТ ДОПСОГЛАШЕНИЯ:
---
{doc_text}
---',
true, 'Авто-создан из llm_prompt.py _build_diff'
)
</cfquery>
</cfif>
<cfcatch></cfcatch>
</cftry>
<cfparam name="url.action" default="get_active">
<cfset result = {ok: false, error: ""}>
<cftry>
<cfif url.action EQ "get_active" AND structKeyExists(url, "role")>
<!--- Получить активный промпт для роли --->
<cfquery name="q" datasource="baza">
SELECT id, name, body, created_at, notes
FROM prompts
WHERE role = <cfqueryparam value="#url.role#" cfsqltype="cf_sql_varchar">
AND is_active = true
ORDER BY created_at DESC
LIMIT 1
</cfquery>
<cfif q.recordCount GT 0>
<cfset result = {ok: true, id: q.id, name: q.name, body: q.body, created_at: q.created_at, notes: q.notes}>
<cfelse>
<cfset result = {ok: true, id: "", name: "", body: "", created_at: "", notes: ""}>
</cfif>
<cfelseif url.action EQ "list" AND structKeyExists(url, "role")>
<!--- Все версии для роли (история) --->
<cfquery name="q" datasource="baza">
SELECT id, name, is_active, created_at, created_by, notes
FROM prompts
WHERE role = <cfqueryparam value="#url.role#" cfsqltype="cf_sql_varchar">
ORDER BY created_at DESC
</cfquery>
<cfset versions = []>
<cfloop query="q">
<cfset arrayAppend(versions, {
id: q.id, name: q.name, is_active: q.is_active,
created_at: q.created_at, created_by: q.created_by, notes: q.notes
})>
</cfloop>
<cfset result = {ok: true, versions: versions}>
<cfelseif url.action EQ "get" AND structKeyExists(url, "id")>
<!--- Получить конкретную версию --->
<cfquery name="q" datasource="baza">
SELECT id, role, name, body, parent_id, is_active, created_at, created_by, notes
FROM prompts
WHERE id = <cfqueryparam value="#url.id#" cfsqltype="cf_sql_varchar">
</cfquery>
<cfif q.recordCount GT 0>
<cfset result = {ok: true, id: q.id, role: q.role, name: q.name, body: q.body,
parent_id: q.parent_id, is_active: q.is_active, created_at: q.created_at,
created_by: q.created_by, notes: q.notes}>
<cfelse>
<cfset result.error = "prompt not found">
</cfif>
<cfelseif url.action EQ "save" AND cgi.request_method EQ "POST">
<!--- Создать новую версию (старая остаётся в истории) --->
<cfset body = toString(getHttpRequestData().content)>
<cfset data = deserializeJSON(body)>
<cfset role = structKeyExists(data, "role") ? data.role : "diff">
<cfset pname = structKeyExists(data, "name") ? data.name : "unnamed">
<cfset pbody = structKeyExists(data, "body") ? data.body : "">
<cfset parentId = structKeyExists(data, "parent_id") AND len(data.parent_id) ? data.parent_id : "">
<cfset pnotes = structKeyExists(data, "notes") ? data.notes : "">
<cfset makeActive = structKeyExists(data, "is_active") ? data.is_active : true>
<cfif NOT len(trim(pbody))>
<cfset result.error = "body required">
<cfelse>
<cftransaction>
<!--- Если делаем активным — сбросить флаг у всех той же роли --->
<cfif makeActive>
<cfquery datasource="baza">
UPDATE prompts SET is_active = false
WHERE role = <cfqueryparam value="#role#" cfsqltype="cf_sql_varchar">
</cfquery>
</cfif>
<cfquery datasource="baza" result="ins">
INSERT INTO prompts (role, name, body, parent_id, is_active, created_by, notes)
VALUES (
<cfqueryparam value="#role#" cfsqltype="cf_sql_varchar">,
<cfqueryparam value="#pname#" cfsqltype="cf_sql_varchar">,
<cfqueryparam value="#pbody#" cfsqltype="cf_sql_varchar">,
<cfif len(parentId)><cfqueryparam value="#parentId#" cfsqltype="cf_sql_varchar"><cfelse>NULL</cfif>,
<cfqueryparam value="#makeActive#" cfsqltype="cf_sql_bit">,
<cfqueryparam value="user" cfsqltype="cf_sql_varchar">,
<cfif len(pnotes)><cfqueryparam value="#pnotes#" cfsqltype="cf_sql_varchar"><cfelse>NULL</cfif>
)
</cfquery>
</cftransaction>
<cfset result = {ok: true, id: ins.generatedKey}>
</cfif>
<cfelseif url.action EQ "activate" AND cgi.request_method EQ "POST">
<!--- Активировать конкретную версию --->
<cfset body = toString(getHttpRequestData().content)>
<cfset data = deserializeJSON(body)>
<cfset pid = structKeyExists(data, "id") ? data.id : "">
<cfif NOT len(pid)>
<cfset result.error = "id required">
<cfelse>
<cfquery name="chk" datasource="baza">
SELECT role FROM prompts WHERE id = <cfqueryparam value="#pid#" cfsqltype="cf_sql_varchar">
</cfquery>
<cfif chk.recordCount EQ 0>
<cfset result.error = "prompt not found">
<cfelse>
<cftransaction>
<cfquery datasource="baza">
UPDATE prompts SET is_active = false
WHERE role = <cfqueryparam value="#chk.role#" cfsqltype="cf_sql_varchar">
</cfquery>
<cfquery datasource="baza">
UPDATE prompts SET is_active = true
WHERE id = <cfqueryparam value="#pid#" cfsqltype="cf_sql_varchar">
</cfquery>
</cftransaction>
<cfset result = {ok: true}>
</cfif>
</cfif>
<cfelseif url.action EQ "delete" AND cgi.request_method EQ "POST">
<!--- Удалить неактивную версию (активную нельзя) --->
<cfset body = toString(getHttpRequestData().content)>
<cfset data = deserializeJSON(body)>
<cfset pid = structKeyExists(data, "id") ? data.id : "">
<cfif NOT len(pid)>
<cfset result.error = "id required">
<cfelse>
<cfquery name="chk" datasource="baza">
SELECT is_active FROM prompts WHERE id = <cfqueryparam value="#pid#" cfsqltype="cf_sql_varchar">
</cfquery>
<cfif chk.recordCount EQ 0>
<cfset result.error = "prompt not found">
<cfelseif chk.is_active>
<cfset result.error = "cannot delete active prompt">
<cfelse>
<cfquery datasource="baza">
DELETE FROM prompts WHERE id = <cfqueryparam value="#pid#" cfsqltype="cf_sql_varchar">
</cfquery>
<cfset result = {ok: true}>
</cfif>
</cfif>
<cfelse>
<cfset result.error = "unknown action (use: get_active, list, get, save, activate, delete)">
</cfif>
<cfcatch>
<cfset result = {ok: false, error: cfcatch.message}>
</cfcatch>
</cftry>
<cfoutput>#serializeJSON(result)#</cfoutput>