TL;DR
把「讀判決靠理由不靠輸贏」這套邏輯,落成 Odoo 的資料模型(models)與欄位(fields):
先用結構化欄位把「中間的論證(ratio decidendi / 原則測試)」存好,再回頭把「事實(頭)與結果(尾)」對齊做一致性檢核,形成可追溯、可計算、可教學的誠數據資料庫。
在 Odoo 裡,以上對應到一組自訂模組(Addon)+多個模型(Model)+欄位(Fields)+伺服器動作/排程(Cron)+記錄規則(Record Rules)。
cheng_legal_reasoner
cheng.(例:cheng.case、cheng.rule)cheng.case(案件主檔:承載頭/中/尾與治理)| 欄位 | 型別 | 說明 |
|---|---|---|
name |
Char | 案件可讀代號(顯示用) |
case_id_external |
Char (indexed) | 外部案號/識別碼 |
jurisdiction |
Selection | 轄區(例:US_NJ、US_FL、TW_TP…) |
court_level |
Selection | 法院層級(trial/appeal/supreme) |
decision_date |
Date (indexed) | 判決日期 |
facts_raw |
Text | 事實全文(原始) |
facts_clean |
Text | 去識別/去尾遮蔽後文本 |
issues |
One2many → cheng.issue |
爭點清單 |
reasoning_ids |
One2many → cheng.reasoning |
中間論證結構(多段) |
rules_ids |
Many2many → cheng.rule |
牽涉到的規則/測試 |
std_of_review |
Selection | 審查強度(strict/intermediate/rationality/proportionality) |
axis_freedom |
Float(0~1) | 自由軸(向量化) |
axis_welfare |
Float(0~1) | 福祉軸(向量化) |
axis_virtue |
Float(0~1) | 德性軸(向量化) |
holding_masked |
Boolean | 是否已在文本中遮蔽結果詞 |
holding |
Selection | 尾:處置/結果(label,訓練期隔離使用) |
consistency_score |
Float | 一致性評分(由檢核任務計算) |
leakage_flags |
Integer | 洩漏指標 bitmask(0=安全) |
domain_shift_score |
Float | 場域遷移穩定度(跨年度/轄區) |
notes |
Html | 人工備註與教學說明 |
state |
Selection | 流程狀態(draft/cleaned/reasoned/validated/published) |
cheng.issue(爭點)| 欄位 | 型別 | 說明 |
|---|---|---|
case_id |
Many2one → cheng.case |
所屬案件 |
title |
Char | 爭點標題 |
description |
Text | 爭點描述 |
importance |
Selection | 重要度(low/med/high) |
cheng.rule(規則/測試庫:可重用)| 欄位 | 型別 | 說明 |
|---|---|---|
name |
Char | 規則名稱(例:best_interest_child) |
category |
Selection | 類別(contract/constitutional/tort/procedural/ethics…) |
elements_required |
Json | 要件陣列 |
exceptions |
Json | 例外陣列 |
std_of_review |
Selection | 預設審查強度 |
authority_refs |
One2many → cheng.authority |
法條/判例引用 |
active |
Boolean | 是否啟用 |
cheng.authority(權威參照)| 欄位 | 型別 | 說明 |
|---|---|---|
rule_id |
Many2one → cheng.rule |
所屬規則 |
cite_text |
Char | 引用簡述(例:NJ Supreme Court, 1988) |
link |
Char | URL / 來源 |
pinpoint |
Char | 具體頁碼/段落 |
cheng.reasoning(中間論證段落:把文本變結構)| 欄位 | 型別 | 說明 |
|---|---|---|
case_id |
Many2one → cheng.case |
所屬案件 |
sequence |
Integer | 段落順序 |
summary |
Text | 論證小結 |
axes |
Json | {freedom:0.6,welfare:0.8,virtue:0.3} |
tests_passed |
Json | 透過/未透過之測試要件 |
std_of_review_local |
Selection | 本段採用審查強度 |
dissent_ref |
Boolean | 是否涉及不同意見/協同意見 |
confidence |
Float | 自動抽取/人工標註信心值 |
cheng.guardrail(治理與檢核:把「再回頭砍頭尾」制度化)| 欄位 | 型別 | 說明 |
|---|---|---|
case_id |
Many2one → cheng.case |
所屬案件 |
type |
Selection | counterfactual / leakage_scan / domain_shift |
payload |
Json | 檢測的設定(例:把 economic_coercion=0) |
result_score |
Float | 分數/指標 |
passed |
Boolean | 是否通過 |
log |
Html | 詳細報告 |
facts_raw 與 holding 物理分欄;在 facts_clean 做結果遮蔽(holding_masked=True)。std_of_review,避免推理強度混淆。cheng.guardrail 記錄;回填 consistency_score、leakage_flags。state 管理清洗、抽取、檢核、發布;每步保留審計軌(Odoo 自帶 chatter)。版本:Odoo 16/17 皆可;如用 17 請調整 manifest
version。
__manifest__.pypython
{
"name": "Cheng Legal Reasoner",
"summary": "Law reasoning × AI features for case analysis (去頭去尾、補中間、回頭砍頭尾)",
"version": "16.0.1.0.0",
"author": "Cheng Data",
"depends": ["base", "mail"],
"data": [
"security/ir.model.access.csv",
"views/cheng_case_views.xml",
"views/cheng_rule_views.xml",
"views/cheng_guardrail_views.xml",
"data/ir_cron.xml"
],
"application": True,
}
5.2 models/init.py
from . import law_case
from . import law_rule
from . import law_guardrail
5.3 models/law_case.py
from odoo import models, fields, api
from odoo.exceptions import ValidationError
import json
STD = [
('strict','Strict Scrutiny'),
('intermediate','Intermediate'),
('rationality','Rational Basis'),
('proportionality','Proportionality')
]
class ChengCase(models.Model):
_name = "cheng.case"
_description = "Cheng Case"
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = "decision_date desc, id desc"
name = fields.Char(required=True, tracking=True)
case_id_external = fields.Char(index=True)
jurisdiction = fields.Selection([
('US_NJ','US - New Jersey'),
('US_FL','US - Florida'),
('TW_TP','Taiwan - Taipei'),
], index=True)
court_level = fields.Selection([
('trial','Trial'),('appeal','Appeal'),('supreme','Supreme')
], index=True)
decision_date = fields.Date(index=True)
facts_raw = fields.Text()
facts_clean = fields.Text()
holding_masked = fields.Boolean(default=False, tracking=True)
issue_ids = fields.One2many("cheng.issue","case_id")
reasoning_ids = fields.One2many("cheng.reasoning","case_id")
rule_ids = fields.Many2many("cheng.rule", "cheng_case_rule_rel", "case_id","rule_id")
std_of_review = fields.Selection(STD, tracking=True)
axis_freedom = fields.Float(digits=(3,2))
axis_welfare = fields.Float(digits=(3,2))
axis_virtue = fields.Float(digits=(3,2))
holding = fields.Selection([
('affirm','Affirmed'),
('reverse','Reversed'),
('remand','Remanded'),
('mixed','Mixed'),
])
consistency_score = fields.Float(digits=(4,3))
leakage_flags = fields.Integer(default=0, help="bitmask, 0 = safe")
domain_shift_score = fields.Float(digits=(4,3))
notes = fields.Html()
state = fields.Selection([
('draft','Draft'),
('cleaned','Cleaned'),
('reasoned','Reasoned'),
('validated','Validated'),
('published','Published'),
], default='draft', tracking=True)
@api.constrains('axis_freedom','axis_welfare','axis_virtue')
def _check_axes(self):
for rec in self:
for v in [rec.axis_freedom, rec.axis_welfare, rec.axis_virtue]:
if v and (v < 0 or v > 1):
raise ValidationError("Axes must be between 0 and 1.")
class ChengIssue(models.Model):
_name = "cheng.issue"
_description = "Cheng Issue"
case_id = fields.Many2one("cheng.case", required=True, ondelete="cascade")
title = fields.Char(required=True)
description = fields.Text()
importance = fields.Selection([
('low','Low'),('med','Medium'),('high','High')
], default='med')
class ChengReasoning(models.Model):
_name = "cheng.reasoning"
_description = "Cheng Reasoning"
case_id = fields.Many2one("cheng.case", required=True, ondelete="cascade")
sequence = fields.Integer(default=10)
summary = fields.Text()
axes = fields.Json() # {"freedom":0.6,"welfare":0.8,"virtue":0.3}
tests_passed = fields.Json() # [{"rule":"best_interest_child","ok":true}]
std_of_review_local = fields.Selection(STD)
dissent_ref = fields.Boolean(default=False)
confidence = fields.Float(digits=(3,2))
5.4 models/law_rule.py
from odoo import models, fields
class ChengRule(models.Model):
_name = "cheng.rule"
_description = "Reusable Rules/Tests"
name = fields.Char(required=True)
category = fields.Selection([
('contract','Contract'),('constitutional','Constitutional'),
('tort','Tort'),('procedural','Procedural'),('ethics','Ethics')
], default='contract')
elements_required = fields.Json() # ["info_asymmetry","economic_coercion"]
exceptions = fields.Json() # ["public_policy_exception"]
std_of_review = fields.Selection([
('strict','Strict Scrutiny'),
('intermediate','Intermediate'),
('rationality','Rational Basis'),
('proportionality','Proportionality')
])
authority_ids = fields.One2many("cheng.authority","rule_id")
active = fields.Boolean(default=True)
class ChengAuthority(models.Model):
_name = "cheng.authority"
_description = "Authority Citations"
rule_id = fields.Many2one("cheng.rule", required=True, ondelete="cascade")
cite_text = fields.Char(required=True)
link = fields.Char()
pinpoint = fields.Char()
5.5 models/law_guardrail.py
from odoo import models, fields
class ChengGuardrail(models.Model):
_name = "cheng.guardrail"
_description = "Consistency & Leakage Checks"
case_id = fields.Many2one("cheng.case", required=True, ondelete="cascade")
type = fields.Selection([
('counterfactual','Counterfactual'),
('leakage_scan','Leakage Scan'),
('domain_shift','Domain Shift')
], required=True)
payload = fields.Json() # e.g., {"economic_coercion":0}
result_score = fields.Float(digits=(4,3))
passed = fields.Boolean(default=False)
log = fields.Html()
5.6 security/ir.model.access.csv
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_case_user,access_case_user,model_cheng_case,base.group_user,1,1,1,0
access_issue_user,access_issue_user,model_cheng_issue,base.group_user,1,1,1,0
access_reasoning_user,access_reasoning_user,model_cheng_reasoning,base.group_user,1,1,1,0
access_rule_user,access_rule_user,model_cheng_rule,base.group_user,1,1,1,0
access_authority_user,access_authority_user,model_cheng_authority,base.group_user,1,1,1,0
access_guardrail_user,access_guardrail_user,model_cheng_guardrail,base.group_user,1,1,1,0
5.7 views/cheng_case_views.xml(表單重點)
5.8 views/cheng_rule_views.xml(規則庫)
5.9 views/cheng_guardrail_views.xml(一致性/洩漏檢核)
5.10 data/ir_cron.xml(排程:自動一致性檢核)
你可以在 cheng.case 加上 def _cron_consistency_check(self): 去呼叫內部的反事實測試與洩漏掃描,把結果寫回 cheng.guardrail 與 consistency_score、leakage_flags。
可解釋 AI:axis_* + tests_passed = 模型可用的人文可讀特徵。
可追溯管制:holding_masked+leakage_flags 確保不偷看答案。
可遷移評估:domain_shift_score 量化在跨年度/轄區的穩定性。
可迭代教學:reasoning_ids 的段落化摘要+信心值,讓學生、助教能對照法官推理。
可治理:cheng.guardrail 保留每次檢核的設定與日誌,形成審計軌。
草稿匯入:建立 cheng.case,填 facts_raw、case_id_external。
清洗:產生 facts_clean,設 holding_masked=True,進入 state=cleaned。
補中間:標註 issue_ids、reasoning_ids、rule_ids、axis_*、std_of_review → state=reasoned。
一致性檢核:排程建立 cheng.guardrail 記錄並回填指標 → state=validated。
發布/分析:產生儀表板(Pivot/Kanban/Graph),輸出教學報告/研究圖表 → state=published。
一致性漏斗:各 state 的案件數量與通過率。
三軸雷達:按法院/年度的自由-福祉-德性分布。
規則熱度:cheng.rule 被引用次數、與勝敗關聯。
洩漏零容忍:leakage_flags=0 比例;違規清單。
遷移健康度:domain_shift_score 趨勢。
在 cheng.reasoning.summary 上掛語意索引(外部向量資料庫或 Odoo 第三方插件),建立多視角檢索:
主審意見 / 不同意見 / 協同意見 → 不同向量空間(消融偏差)。
建 cheng.extraction_job(略),把抽取任務、Prompt、模型版本與結果落地(MLOps 追蹤)。
把「先抓中間的論證,再校正頭尾」變成 Odoo 的資料欄位與流程設計,誠數據資料庫就能同時滿足法學可讀性與AI 可計算性:
規則可重用(cheng.rule)、論證可量化(axis_* / tests_passed)、結果可隔離(holding_masked)、一致性可證明(cheng.guardrail)。
一句話:先把「道理」變資料,再讓資料推動更好的道理。