feat: 搭建阶段 0 与阶段 1 基础工程

This commit is contained in:
2026-04-02 16:28:44 +08:00
parent 02ec7c3a03
commit a5b079a673
37 changed files with 7379 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
{
"name": "@cross-ai/domain",
"private": true,
"version": "0.1.0",
"type": "module",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"build": "tsup src/index.ts --dts --format esm,cjs --clean",
"test": "vitest run",
"typecheck": "tsc -p tsconfig.json --noEmit",
"lint": "tsc -p tsconfig.json --noEmit"
}
}
+66
View File
@@ -0,0 +1,66 @@
export const platforms = ["tmall", "jd"] as const;
export type PlatformId = (typeof platforms)[number];
export const searchRequirements = ["none", "recommended", "required"] as const;
export type SearchRequirement = (typeof searchRequirements)[number];
export const taskStatuses = [
"Draft",
"Searching",
"AwaitingConfirmation",
"NoSelection",
"Running",
"Completed",
"PartialCompleted",
"Blocked",
"Failed"
] as const;
export type TaskStatus = (typeof taskStatuses)[number];
export const taskStages = [
"precheck",
"search",
"confirmation",
"session_check",
"crawl",
"normalize",
"analyze",
"publish"
] as const;
export type TaskStage = (typeof taskStages)[number];
export const platformStatuses = [
"Pending",
"SearchBlocked",
"Searching",
"NoResult",
"AwaitingSelection",
"Skipped",
"Selected",
"Blocked",
"Running",
"Completed",
"Failed"
] as const;
export type PlatformStatus = (typeof platformStatuses)[number];
export const executionStatuses = [
"completed",
"blocked",
"failed",
"skipped",
"no_result"
] as const;
export type ExecutionStatus = (typeof executionStatuses)[number];
export const reportableTaskStatuses = ["Completed", "PartialCompleted"] as const;
export type ReportableTaskStatus = (typeof reportableTaskStatuses)[number];
export const confidenceLevels = ["high", "medium", "low"] as const;
export type ConfidenceLevel = (typeof confidenceLevels)[number];
export const sampleFlags = ["sufficient", "insufficient", "partial"] as const;
export type SampleFlag = (typeof sampleFlags)[number];
export const evidenceSourceTypes = ["product", "review"] as const;
export type EvidenceSourceType = (typeof evidenceSourceTypes)[number];
+5
View File
@@ -0,0 +1,5 @@
export * from "./enums";
export * from "./models";
export * from "./platforms";
export * from "./presentation";
export * from "./state-machine";
+86
View File
@@ -0,0 +1,86 @@
import type {
PlatformId,
PlatformStatus,
SearchRequirement,
TaskStage,
TaskStatus
} from "./enums";
export interface CreateTaskInput {
query: string;
perLinkLimit: number;
taskTotalLimit: number;
}
export interface CandidateRecord {
candidateId: string;
platform: PlatformId;
title: string;
price: number;
priceLabel: string;
storeName: string;
productUrl: string;
imageUrl: string;
salesHint: string;
specLabel: string;
highlights: string[];
}
export interface PlatformRunRecord {
platform: PlatformId;
searchRequirement: SearchRequirement;
status: PlatformStatus;
reason?: string | undefined;
candidateCount: number;
selectedCandidateIds: string[];
lastUpdatedAt: string;
}
export interface TaskEventRecord {
eventId: string;
createdAt: string;
type: string;
message: string;
}
export interface SessionReadinessRecord {
platform: PlatformId;
ready: boolean;
searchRequirement: SearchRequirement;
lastPreparedAt?: string | undefined;
}
export interface TaskRecord {
taskId: string;
query: string;
createdAt: string;
updatedAt: string;
perLinkLimit: number;
taskTotalLimit: number;
taskStatus: TaskStatus;
taskStage: TaskStage;
platformRuns: PlatformRunRecord[];
platformCandidates: Record<PlatformId, CandidateRecord[]>;
events: TaskEventRecord[];
reportVersions: number[];
defaultReportVersion?: number | undefined;
latestSuccessfulReportVersion?: number | undefined;
}
export interface HistoryTaskRecord {
taskId: string;
query: string;
taskStatus: TaskStatus;
updatedAt: string;
hasReport: boolean;
defaultReportVersion?: number | undefined;
failedPlatforms: PlatformId[];
blockedPlatforms: PlatformId[];
}
export interface ConfirmTaskPayload {
selections: Array<{
platform: PlatformId;
candidateIds: string[];
}>;
}
+33
View File
@@ -0,0 +1,33 @@
import type { PlatformId, SearchRequirement } from "./enums";
export interface PlatformCatalogEntry {
id: PlatformId;
label: string;
shortLabel: string;
description: string;
searchRequirement: SearchRequirement;
recoveryHint: string;
}
export const platformCatalog = [
{
id: "tmall",
label: "天猫",
shortLabel: "TM",
description: "推荐保持会话以提升搜索稳定性。",
searchRequirement: "recommended",
recoveryHint: "建议先预热会话,再执行候选搜索。"
},
{
id: "jd",
label: "京东",
shortLabel: "JD",
description: "搜索前必须具备有效会话。",
searchRequirement: "required",
recoveryHint: "需要先完成会话准备,否则系统会标记为 SearchBlocked。"
}
] as const satisfies readonly PlatformCatalogEntry[];
export const platformCatalogMap = Object.fromEntries(
platformCatalog.map((entry) => [entry.id, entry])
) as Record<PlatformId, PlatformCatalogEntry>;
+44
View File
@@ -0,0 +1,44 @@
import type { PlatformStatus, TaskStatus } from "./enums";
export type StatusTone =
| "neutral"
| "info"
| "progress"
| "success"
| "warning"
| "blocked"
| "danger"
| "empty";
export const taskStatusToneMap: Record<TaskStatus, StatusTone> = {
Draft: "neutral",
Searching: "info",
AwaitingConfirmation: "info",
NoSelection: "empty",
Running: "progress",
Completed: "success",
PartialCompleted: "warning",
Blocked: "blocked",
Failed: "danger"
};
export const platformStatusToneMap: Record<PlatformStatus, StatusTone> = {
Pending: "neutral",
SearchBlocked: "blocked",
Searching: "info",
NoResult: "empty",
AwaitingSelection: "info",
Skipped: "neutral",
Selected: "progress",
Blocked: "blocked",
Running: "progress",
Completed: "success",
Failed: "danger"
};
export const taskSpine = [
{ id: "input", label: "输入" },
{ id: "confirmation", label: "确认" },
{ id: "execution", label: "执行" },
{ id: "report", label: "报告" }
] as const;
+60
View File
@@ -0,0 +1,60 @@
import type {
ExecutionStatus,
PlatformStatus,
TaskStatus
} from "./enums";
import type { PlatformRunRecord } from "./models";
export function mapPlatformStatusToExecutionStatus(
status: PlatformStatus
): ExecutionStatus {
switch (status) {
case "Completed":
return "completed";
case "SearchBlocked":
case "Blocked":
return "blocked";
case "Failed":
return "failed";
case "Skipped":
return "skipped";
case "NoResult":
return "no_result";
default:
throw new Error(`Platform status ${status} cannot be published to a report.`);
}
}
export function deriveTaskStatusFromConfirmedPlatforms(
platformRuns: PlatformRunRecord[]
): TaskStatus {
const confirmedRuns = platformRuns.filter(
(run) => run.selectedCandidateIds.length > 0
);
if (confirmedRuns.length === 0) {
return "NoSelection";
}
const completedCount = confirmedRuns.filter(
(run) => run.status === "Completed"
).length;
const hasBlocked = confirmedRuns.some(
(run) => run.status === "Blocked" || run.status === "SearchBlocked"
);
const hasFailed = confirmedRuns.some((run) => run.status === "Failed");
if (completedCount === confirmedRuns.length) {
return "Completed";
}
if (completedCount > 0 && (hasBlocked || hasFailed)) {
return "PartialCompleted";
}
if (completedCount === 0 && hasBlocked) {
return "Blocked";
}
return "Failed";
}
@@ -0,0 +1,75 @@
import { describe, expect, it } from "vitest";
import {
deriveTaskStatusFromConfirmedPlatforms,
mapPlatformStatusToExecutionStatus,
type PlatformRunRecord
} from "../src/index";
function createRun(
status: PlatformRunRecord["status"],
selected = true
): PlatformRunRecord {
return {
platform: "tmall",
searchRequirement: "recommended",
status,
candidateCount: 1,
selectedCandidateIds: selected ? ["candidate-1"] : [],
lastUpdatedAt: new Date().toISOString()
};
}
describe("deriveTaskStatusFromConfirmedPlatforms", () => {
it("returns NoSelection when no platform has confirmed links", () => {
expect(
deriveTaskStatusFromConfirmedPlatforms([createRun("Skipped", false)])
).toBe("NoSelection");
});
it("returns Completed when all confirmed platforms are completed", () => {
expect(
deriveTaskStatusFromConfirmedPlatforms([
createRun("Completed"),
createRun("Completed")
])
).toBe("Completed");
});
it("returns PartialCompleted when at least one confirmed platform is completed and another is blocked", () => {
expect(
deriveTaskStatusFromConfirmedPlatforms([
createRun("Completed"),
createRun("Blocked")
])
).toBe("PartialCompleted");
});
it("returns Blocked when all confirmed platforms are blocked", () => {
expect(
deriveTaskStatusFromConfirmedPlatforms([createRun("SearchBlocked")])
).toBe("Blocked");
});
it("returns Failed when all confirmed platforms fail", () => {
expect(
deriveTaskStatusFromConfirmedPlatforms([createRun("Failed")])
).toBe("Failed");
});
});
describe("mapPlatformStatusToExecutionStatus", () => {
it("maps publishable platform statuses", () => {
expect(mapPlatformStatusToExecutionStatus("Completed")).toBe("completed");
expect(mapPlatformStatusToExecutionStatus("SearchBlocked")).toBe("blocked");
expect(mapPlatformStatusToExecutionStatus("Failed")).toBe("failed");
expect(mapPlatformStatusToExecutionStatus("Skipped")).toBe("skipped");
expect(mapPlatformStatusToExecutionStatus("NoResult")).toBe("no_result");
});
it("rejects running states", () => {
expect(() => mapPlatformStatusToExecutionStatus("Running")).toThrow(
/cannot be published/i
);
});
});
+13
View File
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": [
"node",
"vitest/globals"
]
},
"include": [
"src/**/*.ts",
"test/**/*.ts"
]
}