@iola_adm/iola-cli 0.1.75 → 0.1.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/cli.js +92 -13
package/README.md CHANGED
@@ -24,7 +24,7 @@
24
24
  <img alt="CI" src="https://github.com/adm-iola/iola-cli/actions/workflows/ci.yml/badge.svg">
25
25
  </a>
26
26
  <a href="https://github.com/adm-iola/iola-cli/actions/workflows/npm-publish.yml">
27
- <img alt="npm publish" src="https://github.com/adm-iola/iola-cli/actions/workflows/npm-publish.yml/badge.svg">
27
+ <img alt="npm publish" src="https://github.com/adm-iola/iola-cli/actions/workflows/npm-publish.yml/badge.svg?event=release">
28
28
  </a>
29
29
  <a href="https://github.com/adm-iola/iola-cli/blob/main/LICENSE">
30
30
  <img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-yellow.svg">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iola_adm/iola-cli",
3
- "version": "0.1.75",
3
+ "version": "0.1.77",
4
4
  "description": "CLI и AI-агент городского округа Йошкар-Ола.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/adm-iola/iola-cli#readme",
package/src/cli.js CHANGED
@@ -6042,24 +6042,103 @@ async function aiAsk(args, context = {}) {
6042
6042
 
6043
6043
  function buildDirectDataAnswer(question, dataContext) {
6044
6044
  const normalized = question.toLocaleLowerCase("ru-RU");
6045
- if (!/(директор|руководител)/iu.test(normalized)) return "";
6046
- const terms = extractSearchTerms(question).filter((term) => !/^\d+$/.test(term));
6047
- if (terms.length < 2) return "";
6045
+ const requestedFields = detectDirectDataFields(normalized);
6046
+ if (requestedFields.length === 0) return "";
6048
6047
  const rows = [
6049
6048
  ...dataContext.schools.map((item) => ({ layer: "schools", layerName: "школы", ...item })),
6050
6049
  ...dataContext.kindergartens.map((item) => ({ layer: "kindergartens", layerName: "детские сады", ...item })),
6051
6050
  ];
6052
- const matches = rows.filter((item) => {
6053
- const head = String(item.head || "").toLocaleLowerCase("ru-RU");
6054
- return terms.every((term) => head.includes(term));
6055
- });
6056
- if (matches.length !== 1) return "";
6057
- const item = matches[0];
6051
+ const item = pickDirectDataItem(question, dataContext, rows);
6052
+ if (!item) return "";
6053
+ const lines = requestedFields
6054
+ .map((field) => formatDirectDataField(field, item))
6055
+ .filter(Boolean);
6056
+ if (lines.length === 0) return "";
6057
+ const name = getDirectDataItemName(item);
6058
6058
  return [
6059
- `${item.head} — руководитель ${item.name}.`,
6060
- item.address ? `Адрес: ${item.address}.` : "",
6061
- `Источник: слой ${item.layer}, ИНН ${item.inn || "-"}.`,
6062
- ].filter(Boolean).join("\n");
6059
+ ...lines,
6060
+ `Источник: слой ${item.layer}, ${name}, ИНН ${item.inn || "-"}.`,
6061
+ ].join("\n");
6062
+ }
6063
+
6064
+ function detectDirectDataFields(normalizedQuestion) {
6065
+ const fields = [];
6066
+ if (/(директор|руководител|заведующ|кто возглавляет)/iu.test(normalizedQuestion)) fields.push("head");
6067
+ if (/(сайт|website|url|ссылка)/iu.test(normalizedQuestion)) fields.push("website");
6068
+ if (/(телефон|номер телефона|позвонить)/iu.test(normalizedQuestion)) fields.push("phone");
6069
+ if (/(почт|email|e-mail|имейл|электронн)/iu.test(normalizedQuestion)) fields.push("email");
6070
+ if (/(адрес|где находится|расположен)/iu.test(normalizedQuestion)) fields.push("address");
6071
+ if (/(инн)/iu.test(normalizedQuestion)) fields.push("inn");
6072
+ if (/(лиценз)/iu.test(normalizedQuestion)) fields.push("license");
6073
+ return [...new Set(fields)];
6074
+ }
6075
+
6076
+ function pickDirectDataItem(question, dataContext, rows) {
6077
+ const patterns = dataContext.query?.patterns || extractStructuredPatterns(question);
6078
+ const targetLayers = patterns.targetLayers || [];
6079
+ const scopedRows = targetLayers.length > 0 ? rows.filter((item) => targetLayers.includes(item.layer)) : rows;
6080
+
6081
+ for (const inn of patterns.inns || []) {
6082
+ const match = scopedRows.find((item) => String(item.inn || "") === inn);
6083
+ if (match) return match;
6084
+ }
6085
+
6086
+ for (const number of patterns.numbers || []) {
6087
+ const exact = scopedRows.find((item) => itemNameHasNumber(item, number));
6088
+ if (exact) return exact;
6089
+ }
6090
+
6091
+ const terms = extractSearchTerms(question).filter((term) => !/^\d+$/.test(term));
6092
+ if (terms.length > 0) {
6093
+ const personMatches = scopedRows.filter((item) => {
6094
+ const head = String(item.head || item.fns_head_name || "").toLocaleLowerCase("ru-RU");
6095
+ return terms.every((term) => head.includes(term.toLocaleLowerCase("ru-RU")));
6096
+ });
6097
+ if (personMatches.length === 1) return personMatches[0];
6098
+ }
6099
+
6100
+ const confidentRows = scopedRows.filter((item) => {
6101
+ const confidence = Number(item._match?.confidence ?? item.match?.confidence ?? 0);
6102
+ const score = Number(item._match?.score ?? item.match?.score ?? 0);
6103
+ return confidence >= 0.8 || score >= 30;
6104
+ });
6105
+ if (confidentRows.length === 1) return confidentRows[0];
6106
+
6107
+ return null;
6108
+ }
6109
+
6110
+ function itemNameHasNumber(item, number) {
6111
+ const name = String(item.name || item.title || item.fns_full_name || item.fns_short_name || "").toLocaleLowerCase("ru-RU");
6112
+ const escaped = String(number).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6113
+ return new RegExp(`(?:№\\s*${escaped}|\\b(?:школа|сош|лицей|гимназия|сад|детский сад)\\s*№?\\s*${escaped}\\b)`, "iu").test(name);
6114
+ }
6115
+
6116
+ function formatDirectDataField(field, item) {
6117
+ const name = getDirectDataItemName(item);
6118
+ if (field === "head") {
6119
+ const head = item.head || item.fns_head_name;
6120
+ if (!head) return "";
6121
+ const position = item.fns_head_position || (item.layer === "kindergartens" ? "заведующий" : "директор");
6122
+ return `${position}: ${head} (${name}).`;
6123
+ }
6124
+ if (field === "website") return item.website ? `Сайт: ${item.website}` : `Сайт для ${name} в открытых данных не указан.`;
6125
+ if (field === "phone") return item.phone ? `Телефон: ${item.phone}` : `Телефон для ${name} в открытых данных не указан.`;
6126
+ if (field === "email") return item.email ? `Email: ${item.email}` : `Email для ${name} в открытых данных не указан.`;
6127
+ if (field === "address") return item.address || item.legal_address ? `Адрес: ${item.address || item.legal_address}` : `Адрес для ${name} в открытых данных не указан.`;
6128
+ if (field === "inn") return item.inn ? `ИНН: ${item.inn}` : `ИНН для ${name} в открытых данных не указан.`;
6129
+ if (field === "license") {
6130
+ const parts = [
6131
+ item.license_number ? `номер ${item.license_number}` : "",
6132
+ item.license_status ? `статус: ${item.license_status}` : "",
6133
+ item.license_date ? `дата: ${item.license_date}` : "",
6134
+ ].filter(Boolean);
6135
+ return parts.length > 0 ? `Лицензия: ${parts.join(", ")}.` : `Лицензия для ${name} в открытых данных не указана.`;
6136
+ }
6137
+ return "";
6138
+ }
6139
+
6140
+ function getDirectDataItemName(item) {
6141
+ return item.name || item.title || item.fns_short_name || item.fns_full_name || "организация";
6063
6142
  }
6064
6143
 
6065
6144
  async function resolveUsableAiProfile(config, options = {}) {