Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/ci-ml.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ jobs:
sudo apt-get update
# libgl1 replaces libgl1-mesa-glx on Ubuntu 24.04+
sudo apt-get install -y \
libegl1 \
libgl1 \
libusb-1.0-0 \
libglib2.0-0 \
libgeos-dev \
libproj-dev
Expand All @@ -105,6 +107,11 @@ jobs:
pip install -e ".[dev]"
pip install python-docx

# Open3D loads EGL during import, even when only reading PLY on CPU.
# Report missing shared libraries before the same import breaks many tests.
- name: Check Open3D runtime libraries
run: python -c "import open3d; print(open3d.__version__)"

# photogrammetry/ used to be listed here. It has no tracked files - the
# photo path was dropped and its modules deleted - so on a fresh checkout
# the directory does not exist and ruff exits with E902 "cannot find the
Expand Down
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ backend, web, สถานะจริง, known bugs, roadmap. **อ่าน
ด้วย `tlsep` baseline แยก **ลำต้น(wood)/ใบ(leaf)** → วัด **DBH + ความสูง** → คำนวณ
**ชีวมวล→carbon stock→CO₂e estimate** จาก `species_db.csv` หรือ Chave fallback พร้อม provenance
ส่วน PointNet++, photogrammetry, marketplace และ certification ต้องรายงานตามสถานะ Experimental/Planned
สร้างเพื่อแข่ง **NSC 2026 หมวด 14 (อุดมศึกษา)**
เป้าหมายปัจจุบัน: **ถูกวิทยาศาสตร์ ตีพิมพ์ได้ และใช้งานได้จริง** — ดู CLAUDE.md

---

Expand All @@ -31,7 +31,7 @@ apps/web/ Next.js 14 (App Router, TS, Tailwind, shadcn) — landing + das
services/api/ FastAPI (SQLAlchemy async, asyncpg, Pydantic v2, Alembic) — REST + async-job worker
services/ml/ Point-cloud pipeline — tlsep default, PointNet++ Experimental, 8-step pipeline + allometric
docs/ เอกสาร (PROJECT_SPEC.md, ml/, learning/, decisions/, superpowers/)
proposal/ NSC proposal
proposal/ เอกสารข้อเสนอฉบับเดิม (historical)
memory/ project memory
```

Expand Down Expand Up @@ -136,7 +136,7 @@ Mean IoU `0.613`, accuracy `0.831` และ held-out loader ถูกใช้
## Preferences ของผู้ใช้

- ตอบ **ภาษาไทย** (technical terms EN ได้)
- โฟกัส "ทำให้กรรมการ NSC ว้าว" — Deep Tech + visual storytelling
- **ความถูกต้องมาก่อนความน่าประทับใจ** — กรอบ "ทำให้กรรมการว้าว" เลิกใช้แล้ว
- **อย่า over-engineer** — prototype ที่เสร็จ > vision สมบูรณ์แต่ไม่เสร็จ
- ทุก decision ที่มีค่าใช้จ่าย → "นักศึกษาจ่ายไหวไหม"
- verify ก่อนเคลม — รันจริง/ดูผลจริง อย่าเดา
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@
**โน้ตบุ๊ก Acer Predator PHN16-71 ค้างตายสนิท 19 ครั้ง** (15 ครั้งใน 3 วัน 21-23 ส.ค.)
ไม่มี bugcheck ไม่มี dump ไม่มี WHEA

**สาเหตุ (วัดได้แล้ว 23 ส.ค.): ไฟ AC หลุดเข้าหลุดออก + แบตเสื่อม 72% ที่ 53 cycles**
**บันทึกการตรวจ 23 ส.ค.: ไฟ AC หลุดเข้าหลุดออก + แบตเสื่อม 72% ที่ 53 cycles**
Event 105 บันทึก AC หลุด/กลับ 4 ครั้งใน 10 วินาที (20:00:01-20:00:11) แล้วเครื่องตาย 20:13:03
RAM ตัดออกแล้วด้วยการวัด (Memory Diagnostic 23 ส.ค. 13:24 = no errors)
**ไม่ใช่ซอฟต์แวร์ ไม่ใช่ workload** — รายละเอียดใน memory `machine-freeze-root-cause`
Memory Diagnostic 23 ส.ค. 13:24 รายงาน no errors ผลครั้งเดียวไม่ได้ตัดความเป็นไปได้ของปัญหา RAM หรือซอฟต์แวร์ทั้งหมด
รายละเอียดเดิมอยู่ใน memory `machine-freeze-root-cause` รอบตรวจ PR นี้ไม่ได้ตรวจ hardware ใหม่ จึงไม่ยืนยันว่าสาเหตุถูกพิสูจน์ครบหรือแก้ปัญหาแล้ว

- **ห้ามรัน full test suite บนเครื่องนี้** (`pytest tests/` ชุดเต็มใช้ ~29 นาที และเคยทิ้ง
scratch ไว้ 93 GB จนเครื่องอืด) — ให้ GitHub Actions รับไป
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@

### ประเมินชีวมวล คาร์บอน และ CO₂e ของต้นไม้จาก 3D point cloud พร้อมหลักฐานที่ตรวจสอบย้อนกลับได้

<em>NSC 2026 หมวด 14 · Evidence-gated ML · 3D visual verification</em>
<em>Evidence-gated ML · Destructively validated · 3D visual verification</em>

[![NSC 2026](https://img.shields.io/badge/NSC-2026-2D6A4F)](https://www.nstda.or.th/sims)
[![License: MIT](https://img.shields.io/github/license/Remote55/treeq?color=52B788)](LICENSE)
[![CI · ML](https://github.com/Remote55/treeq/actions/workflows/ci-ml.yml/badge.svg)](https://github.com/Remote55/treeq/actions/workflows/ci-ml.yml)
[![CI · API](https://github.com/Remote55/treeq/actions/workflows/ci-api.yml/badge.svg)](https://github.com/Remote55/treeq/actions/workflows/ci-api.yml)
Expand Down Expand Up @@ -190,7 +189,7 @@ apps/web/ Next.js landing, dashboard และ 3D viewer
services/api/ FastAPI synchronous analyze endpoint
services/ml/ 8-stage point-cloud pipeline, training และ evaluation
docs/ master spec, ML evidence, capability matrix และ decisions
proposal/ เอกสารข้อเสนอโครงงาน NSC
proposal/ เอกสารข้อเสนอฉบับเดิม (historical — เก็บไว้เพื่อ trace การตัดสินใจ)
scripts/ truth sync และ report builder
```

Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ export const metadata: Metadata = {
template: '%s | TreeQ Carbon Platform',
},
description:
'แพลตฟอร์มประเมินชีวมวล คาร์บอน และ CO₂e จาก 3D point cloud พร้อมหลักฐานที่ตรวจสอบย้อนกลับได้ — NSC 2026',
'แพลตฟอร์มประเมินชีวมวล คาร์บอน และ CO₂e จาก 3D point cloud พร้อมหลักฐานที่ตรวจสอบย้อนกลับได้ทุกตัวเลข',
keywords: [
'TreeQ Carbon Platform',
'Carbon Stock Estimate',
'LiDAR',
'Point Cloud',
'Deep Learning',
'Terrestrial Laser Scanning',
'Tree Biomass',
'Allometric Equation',
'TGO',
'NSC 2026',
'T-VER',
'Climate Tech',
'Sustainable Innovation',
],
Expand Down
56 changes: 55 additions & 1 deletion apps/web/src/app/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,60 @@ describe('Landing evidence contract', () => {
expect(markup).not.toMatch(/PointNet\+\+[^<]*Default/);
});

// The accuracy panel showed one diameter error: 0.90 cm, from 65 isolated
// trees in Belgium. That number is true and it is not the one this product
// is about. TreeQ is aimed at tropical forest; the tropical cohort has been
// measured — 61 trees felled and weighed in Cameroon — and the panel had no
// way to say so, because scripts/sync_truth.py never emitted the block.
//
// A visitor reading 0.90 cm was reading a temperate figure as the accuracy
// of a tropical product, and could not tell that the shipped gate refuses
// 33 of the 60 measurable trees in the cohort that does apply.
describe('the tropical cohort is not hidden behind the temperate one', () => {
const cameroon = CORE_DEMO_EVIDENCE.validation.cameroon61;

it('states the tropical diameter error beside the temperate one', () => {
const markup = renderToStaticMarkup(<HomePage />);

expect(markup).toContain(cameroon.dbhGateAppliedMaeCm.toFixed(2));
expect(markup).toContain(
CORE_DEMO_EVIDENCE.validation.demol65.dbhMaeCm.toFixed(2),
);
});

it('says how many trees the gate refused', () => {
const markup = renderToStaticMarkup(<HomePage />);

// Without both counts the headline is an average over an unnamed
// subset, and 27 of 60 reads as 60 of 60.
expect(markup).toContain(String(cameroon.gatePassedTrees));
expect(markup).toContain(String(cameroon.gateRefusedTrees));
});

it('names the cohort as tropical and destructively harvested', () => {
const markup = renderToStaticMarkup(<HomePage />);

expect(markup).toContain('เขตร้อน');
});

it('quotes no accuracy figure that is not in the generated evidence', () => {
const markup = renderToStaticMarkup(<HomePage />);

// The ungated mean error is cohort-specific, not an upper error bound.
expect(markup).not.toContain('1.37 ซม. จากต้นไม้ 60');
});

it('distinguishes the cohort, measurable subset and ungated mean without inventing refusal causes', () => {
const markup = renderToStaticMarkup(<HomePage />);

expect(markup).toContain(`ทั้งหมด ${cameroon.trees} ต้น`);
expect(markup).toContain(`วัดได้ ${cameroon.treesMeasured} ต้น`);
expect(markup).toContain('ไม่ใช่ขอบบนของความผิดพลาด');
expect(markup).not.toContain('ส่วนใหญ่เป็นต้นใหญ่ที่มีพูพอน');
expect(markup).not.toContain('ขอบบนของขั้นวัดขนาด');
});
});

it('renders the five evidence-led editorial beats', () => {
const markup = renderToStaticMarkup(<HomePage />);

Expand Down Expand Up @@ -206,4 +260,4 @@ describe('Landing evidence contract', () => {

expect(offenders).toEqual([]);
});
});
});
57 changes: 50 additions & 7 deletions apps/web/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { decimate, parsePly } from '@/lib/ply-loader';
import { LIMITS_LABEL_TH } from '@/lib/upload-limits';

const { baseline, candidate } = CORE_DEMO_EVIDENCE;
const { demol65, pointnetIndependent } = CORE_DEMO_EVIDENCE.validation;
const { demol65, cameroon61, pointnetIndependent } = CORE_DEMO_EVIDENCE.validation;
// The separation quality that describes what ships: the default backend,
// measured on a cohort it never trained on. The Wan held-out numbers belong to
// the candidate and to a split that also picked its best epoch.
Expand Down Expand Up @@ -59,7 +59,11 @@ const JOURNEY = [
technical: [
'วัดเส้นผ่านศูนย์กลางลำต้นที่ระดับ 1.3 เมตร',
'คำนวณปริมาตรจากแบบจำลองทรงกระบอก QSM',
`ค่าคลาดเคลื่อนเฉลี่ย ${demol65.dbhMaeCm.toFixed(2)} ซม. จากต้นไม้จริง 65 ต้น`,
`ค่าคลาดเคลื่อนเฉลี่ย ${demol65.dbhMaeCm.toFixed(2)} ซม. จากต้นไม้เขตอบอุ่น 65 ต้น`,
// The tropical figure belongs beside the temperate one, not instead of
// it: the cohorts answer different questions and both are quoted.
`${cameroon61.dbhGateAppliedMaeCm.toFixed(2)} ซม. จากต้นไม้เขตร้อนที่โค่นและชั่งจริง ` +
`(${cameroon61.gatePassedTrees} ต้นที่ผ่านเกณฑ์คุณภาพการวัด)`,
],
},
{
Expand Down Expand Up @@ -195,7 +199,7 @@ useEffect(() => {
<span className="h-2 w-2 rounded-full bg-moss" />

<span className="text-xs font-semibold tracking-wide text-canopy">
TREEQ CARBON / NSC 2026
TREEQ CARBON / ตรวจสอบย้อนกลับได้ทุกตัวเลข
</span>
</div>

Expand Down Expand Up @@ -544,11 +548,50 @@ useEffect(() => {
/>
</div>

{/* Two cohorts, because they answer different questions and the
tropical one is the question this product is for. The panel used to
carry only the Belgian figure, which is a true number about
temperate isolated trees presented as the accuracy of a platform
aimed at tropical forest. */}
<div className="h-full [&>*]:h-full">
<EvidenceMetric
label="ค่าคลาดเคลื่อนการวัดขนาดลำต้น"
label="ค่าคลาดเคลื่อนการวัดขนาดลำต้น · เขตอบอุ่น"
value={`${demol65.dbhMaeCm.toFixed(2)} ซม.`}
note="ทดสอบการวัดเส้นผ่านศูนย์กลางลำต้นกับต้นไม้จริงแยกเดี่ยวจำนวน 65 ต้น"
note="ทดสอบการวัดเส้นผ่านศูนย์กลางลำต้นกับต้นไม้จริงแยกเดี่ยวจำนวน 65 ต้น (เบลเยียม)"
/>
</div>
</div>

<div className="mt-4 grid items-stretch gap-4 md:grid-cols-3">
<div className="h-full [&>*]:h-full">
<EvidenceMetric
label="ค่าคลาดเคลื่อนการวัดขนาดลำต้น · เขตร้อน"
value={`${cameroon61.dbhGateAppliedMaeCm.toFixed(2)} ซม.`}
note={
`ชุดแคเมอรูนที่โค่นและชั่งจริงทั้งหมด ${cameroon61.trees} ต้น ` +
`วัดได้ ${cameroon61.treesMeasured} ต้น — ค่านี้มาจาก ${cameroon61.gatePassedTrees} ต้นที่ผ่านเกณฑ์คุณภาพการวัด`
}
tone="dark"
/>
</div>

{/* The refusal count is not a caveat, it is half the headline. An
average over the trees that passed says nothing about the trees
that did not, and 27 of 60 reads as 60 of 60 without this. */}
<div className="h-full [&>*]:h-full">
<EvidenceMetric
label="ต้นที่ระบบปฏิเสธไม่วัด"
value={`${cameroon61.gateRefusedTrees} จาก ${cameroon61.treesMeasured}`}
note="ต้นที่ไม่ผ่านเกณฑ์การวัดในชุดนี้ ข้อมูลอ้างอิงไม่มีป้ายกำกับพูพอนรายต้น จึงยังระบุสาเหตุว่าเป็นพูพอนไม่ได้"
tone="lichen"
/>
</div>

<div className="h-full [&>*]:h-full">
<EvidenceMetric
label="ค่าคลาดเคลื่อนถ้าบังคับให้ตอบทุกต้น"
value={`${cameroon61.dbhMaeCm.toFixed(2)} ซม.`}
note={`ค่าเฉลี่ยความผิดพลาดสัมบูรณ์ของ ${cameroon61.treesMeasured} ต้นที่วัดได้เมื่อไม่ใช้เกณฑ์ปฏิเสธ ไม่ใช่ขอบบนของความผิดพลาดรายต้น`}
/>
</div>
</div>
Expand All @@ -566,11 +609,11 @@ useEffect(() => {
TreeQ Carbon Platform
</span>

<span>Prototype for NSC 2026 · หมวด 14</span>
<span>Prototype · ผลลัพธ์เป็นค่าประมาณ ไม่ใช่คาร์บอนเครดิตที่ผ่านการรับรอง</span>
</div>
</footer>

<ScrollToTop />
</div>
);
}
}
13 changes: 13 additions & 0 deletions apps/web/src/generated/core-demo-evidence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ export const CORE_DEMO_EVIDENCE = {
dbhMaeCm: 0.898318,
volumeMapePct: 11.520556,
},
cameroon61: {
trees: 61,
treesMeasured: 60,
dbhGateAppliedMaeCm: 1.369868,
gatePassedTrees: 27,
gateRefusedTrees: 33,
dbhMaeCmSmallStems: 1.683709,
dbhMaeCmSmallStemsN: 31,
dbhMaeCm: 11.254575,
chaveRouteBApePctMedian: 13.999223,
tverRouteBApePctMedian: 20.850468,
chaveMeasurementSharePctMedian: 5.800567,
},
pointnetIndependent: {
verdict: "FAIL_METRICS",
baseline: {
Expand Down
2 changes: 2 additions & 0 deletions docs/PROJECT_SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
- PointNet++: **Experimental**, not promoted; reviewed evidence never changes the default automatically.
- Wan 2021 held-out: Wood IoU `0.418`, Leaf IoU `0.808`, Mean IoU `0.613`, accuracy `0.831`. The held-out loader was also used for best-epoch selection.
- Demol isolated-tree validation (65 trees): DBH MAE `0.898318 cm`; Volume MAPE `11.520556%`. This is not an eight-stage or carbon validation.
- Cameroon destructive tropical validation (61 trees, 60 measurable): DBH MAE `1.369868 cm` over the `27` trees the shipped gate passes, `33` refused; ungated DBH MAE `11.254575 cm` over all `60` measurable trees when forced to answer. This cohort mean is not an upper bound on individual-tree error.
- Cameroon field-geometry allometric AGB, scored against harvested mass: Chave 2014 median APE `13.999223%` against T-VER `20.850468%`. The Chave median paired APE difference (cloud geometry minus field geometry) is `5.800567` percentage points, not a causal error decomposition. These clouds arrive leaf-stripped and are single trees, so this validates neither stage 5 nor stages 1-4, and Cameroon is not Thailand.
- Independent PointNet review: verdict `FAIL_METRICS`; candidate/baseline external macro Wood IoU `0.23728726507501768`/`0.1958779956856453`.
- Independent downstream candidate/baseline: DBH MAE `1.1591405814498605`/`1.1339476465903928` cm; Height MAE `0.9508502244897976`/`0.5433234000000015` m; Volume MAPE `21.74924193798788`/`18.928262273343613`%; measurable trees `49`/`65`.
- Deterministic core demo: `3` trees, `1036.09 kg C`, `3798.99 kg CO2e`; analyzed commit `8cf3058c1f61` with a clean worktree.
Expand Down
Loading
Loading