feat(schema/numeric-methods-and-defaults): implement categorical numeric functions and resolveDefaults

This commit is contained in:
2026-04-27 11:25:43 +00:00
parent 30f20a9f26
commit 0613a190ce
4 changed files with 375 additions and 6 deletions

View File

@@ -1 +1,122 @@
// resolveDefaults, enum numeric methods
import type {
TaskScope,
TaskRisk,
TaskImpact,
} from "../schema/enums.js";
import type { TaskGraphNodeAttributes } from "../schema/graph.js";
import type { ResolvedTaskAttributes } from "../schema/results.js";
// ---------------------------------------------------------------------------
// Numeric mapping tables — match docs/architecture/schemas.md exactly
// ---------------------------------------------------------------------------
// --- TaskScope → cost/token estimates ---
const SCOPE_COST_ESTIMATE: Record<TaskScope, number> = {
single: 1.0,
narrow: 2.0,
moderate: 3.0,
broad: 4.0,
system: 5.0,
};
const SCOPE_TOKEN_ESTIMATE: Record<TaskScope, number> = {
single: 500,
narrow: 1500,
moderate: 3000,
broad: 6000,
system: 10000,
};
// --- TaskRisk → probability/weight ---
const RISK_SUCCESS_PROBABILITY: Record<TaskRisk, number> = {
trivial: 0.98,
low: 0.90,
medium: 0.80,
high: 0.65,
critical: 0.50,
};
// --- TaskImpact → weight ---
const IMPACT_WEIGHT: Record<TaskImpact, number> = {
isolated: 1.0,
component: 1.5,
phase: 2.0,
project: 3.0,
};
// ---------------------------------------------------------------------------
// Standalone numeric functions
// ---------------------------------------------------------------------------
/** Maps TaskScope → costEstimate (1.05.0). */
export function scopeCostEstimate(scope: TaskScope): number {
return SCOPE_COST_ESTIMATE[scope];
}
/** Maps TaskScope → tokenEstimate (50010000). */
export function scopeTokenEstimate(scope: TaskScope): number {
return SCOPE_TOKEN_ESTIMATE[scope];
}
/** Maps TaskRisk → successProbability (0.500.98). */
export function riskSuccessProbability(risk: TaskRisk): number {
return RISK_SUCCESS_PROBABILITY[risk];
}
/** Maps TaskRisk → riskWeight (0.020.50). Guaranteed to equal 1 - riskSuccessProbability(risk). */
export function riskWeight(risk: TaskRisk): number {
return 1 - riskSuccessProbability(risk);
}
/** Maps TaskImpact → impactWeight (1.03.0). */
export function impactWeight(impact: TaskImpact): number {
return IMPACT_WEIGHT[impact];
}
// ---------------------------------------------------------------------------
// resolveDefaults
// ---------------------------------------------------------------------------
/** Default fallbacks for unassessed categorical fields (see graph-model.md). */
const DEFAULT_RISK: TaskRisk = "medium";
const DEFAULT_SCOPE: TaskScope = "narrow";
const DEFAULT_IMPACT: TaskImpact = "isolated";
/**
* Fills in defaults for unassessed categorical fields and computes derived
* numeric values.
*
* - Categorical fields with defaults (risk, scope, impact) are always resolved.
* - Label-only fields (level, priority, status) remain nullable — no default
* value is assigned.
* - Derived fields (costEstimate, tokenEstimate, successProbability,
* riskWeight, impactWeight) are computed from the resolved categorical values.
*
* @param attrs - Partial node attributes with at least a `name` present.
* @returns Fully resolved attributes ready for analysis.
*/
export function resolveDefaults(
attrs: Partial<TaskGraphNodeAttributes> & Pick<TaskGraphNodeAttributes, "name">,
): ResolvedTaskAttributes {
const risk = attrs.risk ?? DEFAULT_RISK;
const scope = attrs.scope ?? DEFAULT_SCOPE;
const impact = attrs.impact ?? DEFAULT_IMPACT;
return {
name: attrs.name,
scope,
risk,
impact,
level: attrs.level ?? null,
priority: attrs.priority ?? null,
status: attrs.status ?? null,
costEstimate: scopeCostEstimate(scope),
tokenEstimate: scopeTokenEstimate(scope),
successProbability: riskSuccessProbability(risk),
riskWeight: riskWeight(risk),
impactWeight: impactWeight(impact),
};
}

View File

@@ -1,4 +1,18 @@
import { Type, type Static } from "@alkdev/typebox";
import { Type, type Static, type TSchema } from "@alkdev/typebox";
import {
TaskScopeEnum,
TaskRiskEnum,
TaskImpactEnum,
TaskLevelEnum,
TaskPriorityEnum,
TaskStatusEnum,
} from "./enums.js";
// --- Nullable helper (also exported from enums.ts, duplicated here for schema locality) ---
/** Wrap a schema to also accept `null`. */
const Nullable = <T extends TSchema>(schema: T) =>
Type.Union([schema, Type.Null()]);
// --- RiskPathResult ---
@@ -96,4 +110,32 @@ export const RiskDistributionResult = Type.Object({
unspecified: Type.Array(Type.String()),
});
/** Distribution of tasks by risk level */
export type RiskDistributionResult = Static<typeof RiskDistributionResult>;
export type RiskDistributionResult = Static<typeof RiskDistributionResult>;
// --- ResolvedTaskAttributes ---
/**
* The output of `resolveDefaults` — all categorical fields resolved to their
* numeric equivalents for use in analysis.
*
* Categorical fields that have defaults (scope, risk, impact) are no longer
* optional — `resolveDefaults` fills them in. Label-only fields (level,
* priority, status) remain nullable since they have no meaningful default.
*/
export const ResolvedTaskAttributes = Type.Object({
name: Type.String(),
scope: TaskScopeEnum,
risk: TaskRiskEnum,
impact: TaskImpactEnum,
level: Nullable(TaskLevelEnum),
priority: Nullable(TaskPriorityEnum),
status: Nullable(TaskStatusEnum),
// Numeric equivalents (always present after resolution):
costEstimate: Type.Number(),
tokenEstimate: Type.Number(),
successProbability: Type.Number(),
riskWeight: Type.Number(),
impactWeight: Type.Number(),
});
/** Inferred type for {@link ResolvedTaskAttributes} schema. */
export type ResolvedTaskAttributes = Static<typeof ResolvedTaskAttributes>;