@iola_adm/iola-cli 0.1.71 → 0.1.72

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +66 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iola_adm/iola-cli",
3
- "version": "0.1.71",
3
+ "version": "0.1.72",
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
@@ -5969,6 +5969,20 @@ async function aiAsk(args, context = {}) {
5969
5969
  const historyEnabled = !options.bare && !options["no-history"] && isFeatureEnabled("sqlite-history");
5970
5970
  const sessionId = historyEnabled && isFeatureEnabled("sessions") ? ensureSessionForAsk(options, providerConfig, question) : null;
5971
5971
  const history = context.history || (sessionId ? getSessionAiHistory(sessionId) : []);
5972
+ const directAnswer = buildDirectDataAnswer(question, dataContext);
5973
+ if (directAnswer) {
5974
+ if (historyEnabled) {
5975
+ recordAskHistory({ question, answer: directAnswer, providerConfig, dataContext, error: "", sessionId });
5976
+ appendSessionExchange(sessionId, question, directAnswer, dataContext, "");
5977
+ }
5978
+ emitEvent(options, "answer", { length: directAnswer.length, sessionId, direct: true });
5979
+ if (options.output) {
5980
+ await assertPermission("writeFiles");
5981
+ await writeFile(options.output, directAnswer, "utf8");
5982
+ }
5983
+ if (!options.quiet) console.log(directAnswer);
5984
+ return directAnswer;
5985
+ }
5972
5986
  const messages = await buildAiMessages(question, dataContext, history, options, config);
5973
5987
  let answer = "";
5974
5988
  let errorMessage = "";
@@ -6018,6 +6032,28 @@ async function aiAsk(args, context = {}) {
6018
6032
  return answer;
6019
6033
  }
6020
6034
 
6035
+ function buildDirectDataAnswer(question, dataContext) {
6036
+ const normalized = question.toLocaleLowerCase("ru-RU");
6037
+ if (!/(директор|руководител)/iu.test(normalized)) return "";
6038
+ const terms = extractSearchTerms(question).filter((term) => !/^\d+$/.test(term));
6039
+ if (terms.length < 2) return "";
6040
+ const rows = [
6041
+ ...dataContext.schools.map((item) => ({ layer: "schools", layerName: "школы", ...item })),
6042
+ ...dataContext.kindergartens.map((item) => ({ layer: "kindergartens", layerName: "детские сады", ...item })),
6043
+ ];
6044
+ const matches = rows.filter((item) => {
6045
+ const head = String(item.head || "").toLocaleLowerCase("ru-RU");
6046
+ return terms.every((term) => head.includes(term));
6047
+ });
6048
+ if (matches.length !== 1) return "";
6049
+ const item = matches[0];
6050
+ return [
6051
+ `${item.head} — руководитель ${item.name}.`,
6052
+ item.address ? `Адрес: ${item.address}.` : "",
6053
+ `Источник: слой ${item.layer}, ИНН ${item.inn || "-"}.`,
6054
+ ].filter(Boolean).join("\n");
6055
+ }
6056
+
6021
6057
  async function resolveUsableAiProfile(config, options = {}) {
6022
6058
  const explicit = Boolean(options.profile || options.provider);
6023
6059
  const providerConfig = resolveAiProfile(config, options);
@@ -6435,8 +6471,8 @@ async function buildDataContext(question) {
6435
6471
  const mcpBaseUrl = await getMcpBaseUrl();
6436
6472
  const [layers, schools, kindergartens] = await Promise.all([
6437
6473
  fetchJson(`${mcpBaseUrl}/mcp-version`),
6438
- fetchJson(`${apiBaseUrl}/schools?limit=100&offset=0`),
6439
- fetchJson(`${apiBaseUrl}/kindergartens?limit=100&offset=0`),
6474
+ fetchAllApiItems(`${apiBaseUrl}/schools`),
6475
+ fetchAllApiItems(`${apiBaseUrl}/kindergartens`),
6440
6476
  ]);
6441
6477
  const queryTerms = extractSearchTerms(question);
6442
6478
  const patterns = extractStructuredPatterns(question);
@@ -6461,6 +6497,18 @@ async function buildDataContext(question) {
6461
6497
  };
6462
6498
  }
6463
6499
 
6500
+ async function fetchAllApiItems(endpoint, limit = 500, maxItems = 5000) {
6501
+ const all = [];
6502
+ for (let offset = 0; offset < maxItems; offset += limit) {
6503
+ const separator = endpoint.includes("?") ? "&" : "?";
6504
+ const payload = await fetchJson(`${endpoint}${separator}limit=${limit}&offset=${offset}`);
6505
+ const items = normalizeItems(payload);
6506
+ all.push(...items);
6507
+ if (items.length < limit) break;
6508
+ }
6509
+ return all;
6510
+ }
6511
+
6464
6512
  function emptyDataContext(question) {
6465
6513
  return {
6466
6514
  enabled: false,
@@ -6481,7 +6529,13 @@ function shouldUseDataContext(question, options = {}) {
6481
6529
  if (/^(привет|здравствуй|здравствуйте|добрый день|доброе утро|добрый вечер|hi|hello|hey)[!.?\s]*$/iu.test(normalized)) return false;
6482
6530
  if (/^(спасибо|благодарю|ок|окей|понял|поняла|ясно|хорошо|да|нет)[!.?\s]*$/iu.test(normalized)) return false;
6483
6531
  if (normalized.length <= 24 && /^(как дела|что нового|ты тут|ты здесь|кто ты)[?.!\s]*$/iu.test(normalized)) return false;
6484
- return /\b(школ|сад|детсад|детский сад|лицей|гимнази|инн|адрес|телефон|почт|email|сайт|лиценз|руководител|директор|слой|слои|данн|отчет|отчёт|выгруз|csv|json|найди|покажи|список|карточк|организац|учрежден|йошкар|ола|петрова|строител|советск|первомайск)\b/iu.test(normalized);
6532
+ const dataKeywords = [
6533
+ "школ", "сад", "детсад", "детский сад", "лицей", "гимнази", "инн", "адрес", "телефон",
6534
+ "почт", "email", "сайт", "лиценз", "руководител", "директор", "слой", "слои", "данн",
6535
+ "отчет", "отчёт", "выгруз", "csv", "json", "найди", "покажи", "список", "карточк",
6536
+ "организац", "учрежден", "йошкар", "ола", "петрова", "строител", "советск", "первомайск",
6537
+ ];
6538
+ return dataKeywords.some((keyword) => normalized.includes(keyword));
6485
6539
  }
6486
6540
 
6487
6541
  function extractSearchTerms(question) {
@@ -6491,7 +6545,13 @@ function extractSearchTerms(question) {
6491
6545
  .split(/\s+/)
6492
6546
  .map((term) => term.trim())
6493
6547
  .filter(Boolean)
6494
- .filter((term) => !["какие", "какая", "какой", "есть", "найди", "покажи", "контакты", "адрес", "телефон", "школы", "школа", "сад", "детский", "детские", "сады", "улица", "ул"].includes(term));
6548
+ .filter((term) => ![
6549
+ "в", "во", "на", "по", "и", "а", "ну", "так", "слушай", "скажи", "подскажи",
6550
+ "какие", "какая", "какой", "каком", "какой", "есть", "найди", "покажи",
6551
+ "контакты", "адрес", "телефон", "школы", "школа", "школе", "сад", "детский",
6552
+ "детские", "сады", "улица", "ул", "директор", "руководитель",
6553
+ ].includes(term))
6554
+ .filter((term) => term.length > 2 || /^\d+$/.test(term));
6495
6555
 
6496
6556
  return normalized.length > 0 ? normalized : [question];
6497
6557
  }
@@ -6539,8 +6599,10 @@ function scoreItem(item, terms, patterns, layer) {
6539
6599
  const text = JSON.stringify(summary).toLocaleLowerCase("ru-RU");
6540
6600
  const name = String(summary.name || "").toLocaleLowerCase("ru-RU");
6541
6601
  const address = String(summary.address || "").toLocaleLowerCase("ru-RU");
6602
+ const head = String(summary.head || "").toLocaleLowerCase("ru-RU");
6542
6603
  const generalTerms = terms.filter((term) => !/^\d+$/.test(term));
6543
6604
  let score = generalTerms.reduce((value, term) => value + (text.includes(term.toLocaleLowerCase("ru-RU")) ? 1 : 0), 0);
6605
+ score += generalTerms.reduce((value, term) => value + (head.includes(term.toLocaleLowerCase("ru-RU")) ? 5 : 0), 0);
6544
6606
 
6545
6607
  for (const inn of patterns.inns) {
6546
6608
  if (String(summary.inn) === inn) {