Skip to content

Instantly share code, notes, and snippets.

@nillpo
Created June 10, 2025 17:35
Show Gist options
  • Select an option

  • Save nillpo/e88715f75998ae7fad97cc50b8f79463 to your computer and use it in GitHub Desktop.

Select an option

Save nillpo/e88715f75998ae7fad97cc50b8f79463 to your computer and use it in GitHub Desktop.
/**
* BLASTAM - Computer Program for Forecasting Occurrence of Rice Leaf Blast
* Based on the research by Takashi HAYASHI and Yukio KOSHIMIZU (1988)
* Tohoku Agricultural Experiment Station Research Report No.78
*/
interface WeatherData {
precipitation: number; // 降水量 (mm)
windVelocity: number; // 風速 (m/s)
sunshineHours: number; // 日照時間 (hours)
temperature: number; // 気温 (°C)
date: Date;
}
interface BlastForecastResult {
infectionProbability: number; // 感染確率 (0-1)
riskLevel: 'Low' | 'Medium' | 'High' | 'Very High';
wetPeriodHours: number; // 濡れ時間
favorableConditions: boolean;
details: {
precipitationFactor: number;
windFactor: number;
sunshineFactor: number;
temperatureFactor: number;
};
}
class BlastamModel {
private readonly CRITICAL_WET_PERIOD = 6; // 臨界濡れ時間(時間)
private readonly OPTIMAL_TEMP_MIN = 20; // 最適温度範囲下限
private readonly OPTIMAL_TEMP_MAX = 28; // 最適温度範囲上限
/**
* いもち病発生予察を実行
* @param weatherData 24時間分の気象データ
* @returns 予察結果
*/
public forecast(weatherData: WeatherData[]): BlastForecastResult {
if (weatherData.length !== 24) {
throw new Error('24時間分の気象データが必要です');
}
// 濡れ時間の計算
const wetPeriodHours = this.calculateWetPeriod(weatherData);
// 各気象要素の評価
const precipitationFactor = this.evaluatePrecipitation(weatherData);
const windFactor = this.evaluateWind(weatherData);
const sunshineFactor = this.evaluateSunshine(weatherData);
const temperatureFactor = this.evaluateTemperature(weatherData);
// 総合的な感染確率の計算
const infectionProbability = this.calculateInfectionProbability(
wetPeriodHours,
precipitationFactor,
windFactor,
sunshineFactor,
temperatureFactor
);
// 好適条件の判定
const favorableConditions = this.assessFavorableConditions(
wetPeriodHours,
precipitationFactor,
temperatureFactor
);
// リスクレベルの決定
const riskLevel = this.determineRiskLevel(infectionProbability);
return {
infectionProbability,
riskLevel,
wetPeriodHours,
favorableConditions,
details: {
precipitationFactor,
windFactor,
sunshineFactor,
temperatureFactor
}
};
}
/**
* 濡れ時間を計算
* 降雨、高湿度、露の条件を考慮
*/
private calculateWetPeriod(weatherData: WeatherData[]): number {
let wetHours = 0;
for (const data of weatherData) {
// 降雨がある場合
if (data.precipitation > 0) {
wetHours += 1;
continue;
}
// 高湿度条件の推定(気温と日照から)
// 低温かつ日照少ない場合は高湿度と仮定
const isHighHumidity = data.temperature < 25 && data.sunshineHours < 0.5;
// 夜間の露形成(日照時間0で気温が低い場合)
const isDewFormation = data.sunshineHours === 0 && data.temperature < 20;
if (isHighHumidity || isDewFormation) {
wetHours += 1;
}
}
return wetHours;
}
/**
* 降水量因子の評価
*/
private evaluatePrecipitation(weatherData: WeatherData[]): number {
const totalPrecipitation = weatherData.reduce((sum, data) => sum + data.precipitation, 0);
// 降水量による重み付け
if (totalPrecipitation === 0) return 0.1;
if (totalPrecipitation < 5) return 0.3;
if (totalPrecipitation < 10) return 0.6;
if (totalPrecipitation < 20) return 0.8;
return 1.0;
}
/**
* 風速因子の評価
*/
private evaluateWind(weatherData: WeatherData[]): number {
const avgWindVelocity = weatherData.reduce((sum, data) => sum + data.windVelocity, 0) / weatherData.length;
// 適度な風速が胞子飛散に有利
if (avgWindVelocity < 1) return 0.3;
if (avgWindVelocity < 3) return 0.8;
if (avgWindVelocity < 5) return 1.0;
if (avgWindVelocity < 7) return 0.7;
return 0.4; // 強風は不利
}
/**
* 日照因子の評価
*/
private evaluateSunshine(weatherData: WeatherData[]): number {
const totalSunshine = weatherData.reduce((sum, data) => sum + data.sunshineHours, 0);
// 日照時間が少ないほど感染に有利
if (totalSunshine < 3) return 1.0;
if (totalSunshine < 6) return 0.7;
if (totalSunshine < 9) return 0.4;
return 0.1;
}
/**
* 温度因子の評価
*/
private evaluateTemperature(weatherData: WeatherData[]): number {
const avgTemperature = weatherData.reduce((sum, data) => sum + data.temperature, 0) / weatherData.length;
// 最適温度範囲での評価
if (avgTemperature >= this.OPTIMAL_TEMP_MIN && avgTemperature <= this.OPTIMAL_TEMP_MAX) {
return 1.0;
}
// 最適範囲からの距離に応じて重み付け
const distanceFromOptimal = Math.min(
Math.abs(avgTemperature - this.OPTIMAL_TEMP_MIN),
Math.abs(avgTemperature - this.OPTIMAL_TEMP_MAX)
);
if (distanceFromOptimal <= 2) return 0.8;
if (distanceFromOptimal <= 5) return 0.5;
if (distanceFromOptimal <= 8) return 0.2;
return 0.05;
}
/**
* 感染確率を計算
*/
private calculateInfectionProbability(
wetPeriodHours: number,
precipitationFactor: number,
windFactor: number,
sunshineFactor: number,
temperatureFactor: number
): number {
// 濡れ時間が臨界値未満の場合は感染確率が大幅に低下
const wetPeriodFactor = wetPeriodHours >= this.CRITICAL_WET_PERIOD ? 1.0 :
Math.pow(wetPeriodHours / this.CRITICAL_WET_PERIOD, 2);
// 各因子の重み付け合成
const baseInfectionRate = (
precipitationFactor * 0.3 +
windFactor * 0.2 +
sunshineFactor * 0.2 +
temperatureFactor * 0.3
);
// 濡れ時間因子を乗算
const infectionProbability = baseInfectionRate * wetPeriodFactor;
return Math.min(Math.max(infectionProbability, 0), 1);
}
/**
* 好適条件の判定
*/
private assessFavorableConditions(
wetPeriodHours: number,
precipitationFactor: number,
temperatureFactor: number
): boolean {
return wetPeriodHours >= this.CRITICAL_WET_PERIOD &&
precipitationFactor >= 0.5 &&
temperatureFactor >= 0.6;
}
/**
* リスクレベルの決定
*/
private determineRiskLevel(infectionProbability: number): 'Low' | 'Medium' | 'High' | 'Very High' {
if (infectionProbability < 0.2) return 'Low';
if (infectionProbability < 0.4) return 'Medium';
if (infectionProbability < 0.7) return 'High';
return 'Very High';
}
/**
* 複数日間の予察を実行
*/
public forecastMultipleDays(weatherDataByDay: WeatherData[][]): BlastForecastResult[] {
return weatherDataByDay.map(dayData => this.forecast(dayData));
}
/**
* 感染好適期間の判定
*/
public findInfectionPeriods(results: BlastForecastResult[]): Array<{start: number, end: number, maxProbability: number}> {
const periods: Array<{start: number, end: number, maxProbability: number}> = [];
let currentPeriodStart = -1;
let maxProbInPeriod = 0;
for (let i = 0; i < results.length; i++) {
const result = results[i];
if (result.favorableConditions) {
if (currentPeriodStart === -1) {
currentPeriodStart = i;
maxProbInPeriod = result.infectionProbability;
} else {
maxProbInPeriod = Math.max(maxProbInPeriod, result.infectionProbability);
}
} else {
if (currentPeriodStart !== -1) {
periods.push({
start: currentPeriodStart,
end: i - 1,
maxProbability: maxProbInPeriod
});
currentPeriodStart = -1;
maxProbInPeriod = 0;
}
}
}
// 最後の期間の処理
if (currentPeriodStart !== -1) {
periods.push({
start: currentPeriodStart,
end: results.length - 1,
maxProbability: maxProbInPeriod
});
}
return periods;
}
}
// 使用例
export { BlastamModel, WeatherData, BlastForecastResult };
// サンプル使用例
/*
const model = new BlastamModel();
// サンプルデータ(24時間分)
const sampleWeatherData: WeatherData[] = Array.from({length: 24}, (_, i) => ({
precipitation: i < 6 ? 2.5 : 0, // 最初の6時間降雨
windVelocity: 2.0 + Math.sin(i * Math.PI / 12), // 風速変動
sunshineHours: i >= 6 && i <= 18 ? 0.5 : 0, // 日中のみ日照
temperature: 22 + 3 * Math.sin((i - 6) * Math.PI / 12), // 温度変動
date: new Date(2024, 5, 15, i, 0, 0)
}));
const result = model.forecast(sampleWeatherData);
console.log('感染確率:', (result.infectionProbability * 100).toFixed(1) + '%');
console.log('リスクレベル:', result.riskLevel);
console.log('濡れ時間:', result.wetPeriodHours + '時間');
console.log('好適条件:', result.favorableConditions ? 'あり' : 'なし');
*/
@nillpo
Copy link
Copy Markdown
Author

nillpo commented Jun 10, 2025

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment